fund distribution

This commit is contained in:
Abin Toppo
2025-09-26 10:16:19 +05:30
parent a303af566e
commit 6778e79dc8
6 changed files with 630 additions and 248 deletions
+171 -29
View File
@@ -28,6 +28,7 @@
"react-native-calendars": "^1.1313.0", "react-native-calendars": "^1.1313.0",
"react-native-dialog": "^9.3.0", "react-native-dialog": "^9.3.0",
"react-native-elements": "^3.4.3", "react-native-elements": "^3.4.3",
"react-native-fast-confetti": "^1.1.1",
"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",
"react-native-linear-gradient": "^2.8.3", "react-native-linear-gradient": "^2.8.3",
@@ -154,7 +155,6 @@
"version": "7.27.3", "version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
"integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.27.3" "@babel/types": "^7.27.3"
@@ -183,7 +183,6 @@
"version": "7.28.3", "version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz",
"integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-annotate-as-pure": "^7.27.3",
@@ -205,7 +204,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz",
"integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-annotate-as-pure": "^7.27.1",
@@ -249,7 +247,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz",
"integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/traverse": "^7.27.1", "@babel/traverse": "^7.27.1",
@@ -293,7 +290,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
"integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.27.1" "@babel/types": "^7.27.1"
@@ -333,7 +329,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz",
"integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-member-expression-to-functions": "^7.27.1",
@@ -351,7 +346,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
"integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/traverse": "^7.27.1", "@babel/traverse": "^7.27.1",
@@ -699,7 +693,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz",
"integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.27.1" "@babel/helper-plugin-utils": "^7.27.1"
@@ -817,7 +810,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
"integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.27.1" "@babel/helper-plugin-utils": "^7.27.1"
@@ -850,7 +842,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz",
"integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.27.1" "@babel/helper-plugin-utils": "^7.27.1"
@@ -934,7 +925,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz",
"integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-create-class-features-plugin": "^7.27.1",
@@ -968,7 +958,6 @@
"version": "7.28.4", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
"integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-annotate-as-pure": "^7.27.3",
@@ -1271,7 +1260,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz",
"integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-module-transforms": "^7.27.1", "@babel/helper-module-transforms": "^7.27.1",
@@ -1357,7 +1345,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz",
"integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.27.1" "@babel/helper-plugin-utils": "^7.27.1"
@@ -1442,7 +1429,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
"integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1",
@@ -1664,7 +1650,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz",
"integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.27.1" "@babel/helper-plugin-utils": "^7.27.1"
@@ -1713,7 +1698,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz",
"integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-plugin-utils": "^7.27.1" "@babel/helper-plugin-utils": "^7.27.1"
@@ -1745,7 +1729,6 @@
"version": "7.28.0", "version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz",
"integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-annotate-as-pure": "^7.27.3",
@@ -1798,7 +1781,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz",
"integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-create-regexp-features-plugin": "^7.27.1",
@@ -1928,6 +1910,26 @@
"@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0"
} }
}, },
"node_modules/@babel/preset-typescript": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz",
"integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-validator-option": "^7.27.1",
"@babel/plugin-syntax-jsx": "^7.27.1",
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
"@babel/plugin-transform-typescript": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.28.4", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
@@ -3654,6 +3656,33 @@
} }
} }
}, },
"node_modules/@shopify/react-native-skia": {
"version": "2.2.19",
"resolved": "https://registry.npmjs.org/@shopify/react-native-skia/-/react-native-skia-2.2.19.tgz",
"integrity": "sha512-K/mui7BEPfOkC4EGLR3tGn7dUGnQQQUBlTPShelTvhCbhvBey1VMiXLJrf3uEkm0nox3PRLgPC8/WyW6kSlxUQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"canvaskit-wasm": "0.40.0",
"react-reconciler": "0.31.0"
},
"bin": {
"setup-skia-web": "scripts/setup-canvaskit.js"
},
"peerDependencies": {
"react": ">=19.0",
"react-native": ">=0.78",
"react-native-reanimated": ">=3.19.1"
},
"peerDependenciesMeta": {
"react-native": {
"optional": true
},
"react-native-reanimated": {
"optional": true
}
}
},
"node_modules/@sideway/address": { "node_modules/@sideway/address": {
"version": "4.1.5", "version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@@ -4127,6 +4156,13 @@
"devOptional": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@webgpu/types": {
"version": "0.1.21",
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.21.tgz",
"integrity": "sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==",
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/@xmldom/xmldom": { "node_modules/@xmldom/xmldom": {
"version": "0.8.11", "version": "0.8.11",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
@@ -5044,6 +5080,16 @@
], ],
"license": "CC-BY-4.0" "license": "CC-BY-4.0"
}, },
"node_modules/canvaskit-wasm": {
"version": "0.40.0",
"resolved": "https://registry.npmjs.org/canvaskit-wasm/-/canvaskit-wasm-0.40.0.tgz",
"integrity": "sha512-Od2o+ZmoEw9PBdN/yCGvzfu0WVqlufBPEWNG452wY7E9aT8RBE+ChpZF526doOlg7zumO4iCS+RAeht4P0Gbpw==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"@webgpu/types": "0.1.21"
}
},
"node_modules/chalk": { "node_modules/chalk": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -13439,6 +13485,21 @@
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-native-fast-confetti": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/react-native-fast-confetti/-/react-native-fast-confetti-1.1.1.tgz",
"integrity": "sha512-5kJn+dgvEoeM9h4c4SqxeqMtYAfN8QhaB1JS/5yv+AGYjVnE1a4c0AAv4yFyetwvmWQ3b16C10a8eW0FlaFAyg==",
"license": "MIT",
"workspaces": [
"example"
],
"peerDependencies": {
"@shopify/react-native-skia": "*",
"react": "*",
"react-native": "*",
"react-native-reanimated": "*"
}
},
"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",
@@ -13556,6 +13617,36 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/react-native-reanimated": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.2.tgz",
"integrity": "sha512-qzmQiFrvjm62pRBcj97QI9Xckc3EjgHQoY1F2yjktd0kpjhoyePeuTEXjYRCAVIy7IV/1cfeSup34+zFThFoHQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"react-native-is-edge-to-edge": "^1.2.1",
"semver": "7.7.2"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0",
"react": "*",
"react-native": "*",
"react-native-worklets": ">=0.5.0"
}
},
"node_modules/react-native-reanimated/node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
"peer": true,
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/react-native-safe-area-context": { "node_modules/react-native-safe-area-context": {
"version": "5.6.1", "version": "5.6.1",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz", "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz",
@@ -13683,6 +13774,44 @@
"@types/react": ">=16.8.24" "@types/react": ">=16.8.24"
} }
}, },
"node_modules/react-native-worklets": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.6.0.tgz",
"integrity": "sha512-yETMNuCcivdYWteuG4eRqgiAk2DzRCrVAaEBIEWPo4emrf3BNjadFo85L5QvyEusrX9QKE3ZEAx8U5A/nbyFgg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/plugin-transform-arrow-functions": "^7.0.0-0",
"@babel/plugin-transform-class-properties": "^7.0.0-0",
"@babel/plugin-transform-classes": "^7.0.0-0",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0",
"@babel/plugin-transform-optional-chaining": "^7.0.0-0",
"@babel/plugin-transform-shorthand-properties": "^7.0.0-0",
"@babel/plugin-transform-template-literals": "^7.0.0-0",
"@babel/plugin-transform-unicode-regex": "^7.0.0-0",
"@babel/preset-typescript": "^7.16.7",
"convert-source-map": "^2.0.0",
"semver": "7.7.2"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0",
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-worklets/node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"license": "ISC",
"peer": true,
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/react-native/node_modules/commander": { "node_modules/react-native/node_modules/commander": {
"version": "12.1.0", "version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
@@ -13704,6 +13833,29 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/react-reconciler": {
"version": "0.31.0",
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.31.0.tgz",
"integrity": "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.25.0"
},
"engines": {
"node": ">=0.10.0"
},
"peerDependencies": {
"react": "^19.0.0"
}
},
"node_modules/react-reconciler/node_modules/scheduler": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
"license": "MIT",
"peer": true
},
"node_modules/react-redux": { "node_modules/react-redux": {
"version": "9.2.0", "version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
@@ -13822,14 +13974,12 @@
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
"integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/regenerate-unicode-properties": { "node_modules/regenerate-unicode-properties": {
"version": "10.2.2", "version": "10.2.2",
"resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz",
"integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"regenerate": "^1.4.2" "regenerate": "^1.4.2"
@@ -13869,7 +14019,6 @@
"version": "6.3.1", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.3.1.tgz", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.3.1.tgz",
"integrity": "sha512-DzcswPr252wEr7Qz8AyAVbfyBDKLoYp6eRA1We2Fa9qirRFSdtkP5sHr3yglDKy2BbA0fd2T+j/CUSKes3FeVQ==", "integrity": "sha512-DzcswPr252wEr7Qz8AyAVbfyBDKLoYp6eRA1We2Fa9qirRFSdtkP5sHr3yglDKy2BbA0fd2T+j/CUSKes3FeVQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"regenerate": "^1.4.2", "regenerate": "^1.4.2",
@@ -13887,14 +14036,12 @@
"version": "0.8.0", "version": "0.8.0",
"resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
"integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/regjsparser": { "node_modules/regjsparser": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz",
"integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==",
"devOptional": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"jsesc": "~3.0.2" "jsesc": "~3.0.2"
@@ -13907,7 +14054,6 @@
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
"integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"jsesc": "bin/jsesc" "jsesc": "bin/jsesc"
@@ -15185,7 +15331,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
"integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
@@ -15195,7 +15340,6 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
"integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-canonical-property-names-ecmascript": "^2.0.0",
@@ -15209,7 +15353,6 @@
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz",
"integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
@@ -15219,7 +15362,6 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz",
"integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
+1
View File
@@ -30,6 +30,7 @@
"react-native-calendars": "^1.1313.0", "react-native-calendars": "^1.1313.0",
"react-native-dialog": "^9.3.0", "react-native-dialog": "^9.3.0",
"react-native-elements": "^3.4.3", "react-native-elements": "^3.4.3",
"react-native-fast-confetti": "^1.1.1",
"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",
"react-native-linear-gradient": "^2.8.3", "react-native-linear-gradient": "^2.8.3",
+2
View File
@@ -19,6 +19,7 @@ import ManageJoinedGroup from "../screens/management/ManageJoinedGroup";
import InviteScreen from "../screens/InviteScreen"; import InviteScreen from "../screens/InviteScreen";
import ManagementTabNavigation from "../screens/management/ManagementTabNavigation"; import ManagementTabNavigation from "../screens/management/ManagementTabNavigation";
import TransactionsList from "../screens/transaction/TransactionList"; import TransactionsList from "../screens/transaction/TransactionList";
import RandomSelectUserScreen from "../screens/management/RandomSelectUserScreen";
export type RootStackParamList = { export type RootStackParamList = {
Splash: undefined; Splash: undefined;
@@ -91,6 +92,7 @@ const linking = {
<Stack.Screen name="Invite" component={InviteScreen} options={{ headerShown: false }} /> <Stack.Screen name="Invite" component={InviteScreen} options={{ headerShown: false }} />
<Stack.Screen name="ManagementTabs" component={ManagementTabNavigation} options={{ headerShown: false }} /> <Stack.Screen name="ManagementTabs" component={ManagementTabNavigation} options={{ headerShown: false }} />
<Stack.Screen name="TransactionList" component={TransactionsList} options={{ headerShown: false }} /> <Stack.Screen name="TransactionList" component={TransactionsList} options={{ headerShown: false }} />
<Stack.Screen name="RandomSelectUserScreen" component={RandomSelectUserScreen} options={{ headerShown: false }} />
</Stack.Navigator> </Stack.Navigator>
</NavigationContainer> </NavigationContainer>
); );
+45 -19
View File
@@ -21,6 +21,7 @@ import { CompositeScreenProps } from '@react-navigation/native';
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs'; import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
import { ManagementTabParamList } from './ManagementTabNavigation'; // Adjust path import { ManagementTabParamList } from './ManagementTabNavigation'; // Adjust path
// --- Type Definitions --- // --- Type Definitions ---
interface Participant { interface Participant {
id: string; id: string;
@@ -30,6 +31,7 @@ interface Participant {
role: 'ORGANIZER' | 'USER'; role: 'ORGANIZER' | 'USER';
} }
export interface GroupDetails { export interface GroupDetails {
id: string; id: string;
groupName: string; groupName: string;
@@ -53,6 +55,7 @@ export interface GroupDetails {
participants: Participant[]; participants: Participant[];
} }
export interface EditGroupPayload { export interface EditGroupPayload {
groupName: string; groupName: string;
description: string; description: string;
@@ -66,18 +69,22 @@ export interface EditGroupPayload {
lateFeeValue: number | null; lateFeeValue: number | null;
} }
// Correct props for a screen inside a nested Tab Navigator // Correct props for a screen inside a nested Tab Navigator
type EditGroupScreenProps = CompositeScreenProps< type EditGroupScreenProps = CompositeScreenProps<
BottomTabScreenProps<ManagementTabParamList, 'EditGroup'>, BottomTabScreenProps<ManagementTabParamList, 'EditGroup'>,
NativeStackScreenProps<RootStackParamList> NativeStackScreenProps<RootStackParamList>
>; >;
type EditingCard = 'groupInfo' | 'financialRules' | 'configuration' | null; type EditingCard = 'groupInfo' | 'financialRules' | 'configuration' | null;
// --- Main Screen Component --- // --- Main Screen Component ---
const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => { const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
const { groupId } = route.params; const { groupId } = route.params;
const [editingCard, setEditingCard] = useState<EditingCard>(null); const [editingCard, setEditingCard] = useState<EditingCard>(null);
const [groupDetails, setGroupDetails] = useState<GroupDetails | null>(null); const [groupDetails, setGroupDetails] = useState<GroupDetails | null>(null);
const [editableData, setEditableData] = useState<EditGroupPayload>({ const [editableData, setEditableData] = useState<EditGroupPayload>({
@@ -99,7 +106,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
const [inviting, setInviting] = useState(false); const [inviting, setInviting] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
// ... (All your handler functions: initializeEditableData, fetchGroupDetails, handleCancel, handleSave, etc. remain the same)
const initializeEditableData = (data: GroupDetails) => { const initializeEditableData = (data: GroupDetails) => {
setEditableData({ setEditableData({
groupName: data.groupName, groupName: data.groupName,
@@ -115,6 +122,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
}); });
}; };
const fetchGroupDetails = useCallback(async () => { const fetchGroupDetails = useCallback(async () => {
setLoading(true); setLoading(true);
setError(null); setError(null);
@@ -134,10 +142,12 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
} }
}, [groupId]); }, [groupId]);
useEffect(() => { useEffect(() => {
fetchGroupDetails(); fetchGroupDetails();
}, [fetchGroupDetails]); }, [fetchGroupDetails]);
const handleCancel = () => { const handleCancel = () => {
if (groupDetails) { if (groupDetails) {
initializeEditableData(groupDetails); initializeEditableData(groupDetails);
@@ -145,9 +155,11 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
setEditingCard(null); setEditingCard(null);
}; };
const handleSave = async () => { const handleSave = async () => {
if (!groupDetails) return; if (!groupDetails) return;
if (editableData.maxParticipants < groupDetails.totalParticipants) { if (editableData.maxParticipants < groupDetails.totalParticipants) {
Alert.alert('Invalid Input', `Max participants cannot be less than the current count (${groupDetails.totalParticipants}).`); Alert.alert('Invalid Input', `Max participants cannot be less than the current count (${groupDetails.totalParticipants}).`);
return; return;
@@ -166,9 +178,11 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
} }
}; };
const handleStartGroup = async () => { const handleStartGroup = async () => {
if (!groupDetails) return; if (!groupDetails) return;
Alert.alert( Alert.alert(
"Confirm Start", "Confirm Start",
"Are you sure you want to start this group? This action cannot be undone.", "Are you sure you want to start this group? This action cannot be undone.",
@@ -200,6 +214,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
try { try {
const response = await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/invite/generate?groupId=${groupDetails.id}`, 'GET') as { data?: string }; const response = await apiCall(`https://exotic-humpback-mistakenly.ngrok-free.app/api/invite/generate?groupId=${groupDetails.id}`, 'GET') as { data?: string };
const inviteLink = response.data; const inviteLink = response.data;
if (inviteLink) { if (inviteLink) {
@@ -220,12 +235,13 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
} }
}; };
const handleUpdateField = (field: keyof EditGroupPayload, value: any) => { const handleUpdateField = (field: keyof EditGroupPayload, value: any) => {
setEditableData(prev => ({ ...prev, [field]: value })); setEditableData(prev => ({ ...prev, [field]: value }));
}; };
// ... (renderField and renderCard functions also remain the same)
const renderField = (field: keyof EditGroupPayload, label: string, displayValue: string | number, isEditing: boolean) => { const renderField = (field: keyof EditGroupPayload, label: string, displayValue: string | number, isEditing: boolean, isEditable: boolean) => {
const commonInputProps = { style: styles.input, placeholderTextColor: "#9CA3AF" }; const commonInputProps = { style: styles.input, placeholderTextColor: "#9CA3AF" };
const getInputForField = () => { const getInputForField = () => {
@@ -276,6 +292,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
const renderCard = (title: string, fields: Array<{ field: keyof EditGroupPayload, label: string, displayValue: () => string | number }>, cardKey: EditingCard) => { const renderCard = (title: string, fields: Array<{ field: keyof EditGroupPayload, label: string, displayValue: () => string | number }>, cardKey: EditingCard) => {
const isEditingThisCard = editingCard === cardKey; const isEditingThisCard = editingCard === cardKey;
return ( return (
<View style={styles.card}> <View style={styles.card}>
<View style={styles.cardHeader}> <View style={styles.cardHeader}>
@@ -288,7 +305,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
</View> </View>
{fields.map(({ field, label, displayValue }) => {fields.map(({ field, label, displayValue }) =>
renderField(field, label, displayValue(), isEditingThisCard) renderField(field, label, displayValue(), isEditingThisCard, isEditable)
)} )}
{isEditingThisCard && ( {isEditingThisCard && (
@@ -305,16 +322,18 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
); );
} }
if (loading) return <View style={styles.centered}><ActivityIndicator size="large" color="#4F46E5" /></View>; 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 (error) return <View style={styles.centered}><Text style={styles.errorText}>{error}</Text></View>;
if (!groupDetails) return null; if (!groupDetails) return null;
const isEditable = groupDetails.status === 'PENDING' || groupDetails.status === 'APPROVED'; const isEditable = groupDetails.status === 'PENDING' || groupDetails.status === 'APPROVED';
const isInvitable = groupDetails.status === 'APPROVED'; const isInvitable = groupDetails.status === 'APPROVED';
const canStartGroup = groupDetails.status === 'APPROVED' && groupDetails.totalParticipants >= groupDetails.minParticipants; const canStartGroup = groupDetails.status === 'APPROVED' && groupDetails.totalParticipants >= groupDetails.minParticipants;
return ( return (
// Use AppView as the root container with flex: 1
<AppView style={{ flex: 1, paddingHorizontal: 0 }}> <AppView style={{ flex: 1, paddingHorizontal: 0 }}>
<View style={styles.headerContainer}> <View style={styles.headerContainer}>
<Text style={styles.headerTitle}>Manage Group</Text> <Text style={styles.headerTitle}>Manage Group</Text>
@@ -325,6 +344,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
)} )}
</View> </View>
<ScrollView <ScrollView
style={styles.container} style={styles.container}
contentContainerStyle={styles.contentContainer} contentContainerStyle={styles.contentContainer}
@@ -335,20 +355,29 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
{ field: 'description', label: 'Description', displayValue: () => groupDetails.description }, { field: 'description', label: 'Description', displayValue: () => groupDetails.description },
], 'groupInfo')} ], 'groupInfo')}
{renderCard('Financial Rules', [
{groupDetails.status === 'ACTIVE' ?
renderCard('Financial Rules', [
{ field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` },
{ field: 'latePaymentCharge', label: 'Late Fee', displayValue: () => `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}` },
{ field: 'maxParticipants', label: 'Current Participants', displayValue: () => `${groupDetails.totalParticipants}` },
], 'financialRules')
:
renderCard('Financial Rules', [
{ field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` }, { field: 'contributionAmount', label: 'Contribution', displayValue: () => `₹${groupDetails.contributionAmount.toLocaleString('en-IN')}` },
{ field: 'maxParticipants', label: 'Max Participants', displayValue: () => `${groupDetails.totalParticipants} / ${groupDetails.maxParticipants}` }, { field: 'maxParticipants', label: 'Max Participants', displayValue: () => `${groupDetails.totalParticipants} / ${groupDetails.maxParticipants}` },
{ field: 'minParticipants', label: 'Min Participants', displayValue: () => `${groupDetails.minParticipants}` }, { field: 'minParticipants', label: 'Min Participants', displayValue: () => `${groupDetails.minParticipants}` },
{ field: 'latePaymentCharge', label: 'Late Fee', displayValue: () => `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}` }, { field: 'latePaymentCharge', label: 'Late Fee', displayValue: () => `₹${(groupDetails.latePaymentCharge ?? 0).toLocaleString('en-IN')}` },
], 'financialRules')} ], 'financialRules')
}
{renderCard('Configuration', [ {renderCard('Configuration', [
{ field: 'cycleType', label: 'Payment Cycle', displayValue: () => groupDetails.cycleType }, { field: 'cycleType', label: 'Payment Cycle', displayValue: () => groupDetails.cycleType },
{ field: 'customCycleDays', label: 'Custom Days', displayValue: () => `${groupDetails.customCycleDays ?? ''} days` }, ...(groupDetails.cycleType === 'CUSTOM' ? [{ field: 'customCycleDays' as keyof EditGroupPayload, label: 'Custom Days', displayValue: () => `${groupDetails.customCycleDays} days` }] : []),
{ field: 'visibility', label: 'Visibility', displayValue: () => groupDetails.visibility }, { field: 'visibility', label: 'Visibility', displayValue: () => groupDetails.visibility },
], 'configuration')} ], 'configuration')}
{/* Participants Card ... */}
<View style={styles.card}> <View style={styles.card}>
<Text style={styles.cardTitle}>Participants ({groupDetails.totalParticipants})</Text> <Text style={styles.cardTitle}>Participants ({groupDetails.totalParticipants})</Text>
{groupDetails.participants.length > 0 ? ( {groupDetails.participants.length > 0 ? (
@@ -369,8 +398,7 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
</View> </View>
</ScrollView> </ScrollView>
{/* --- FLOATING BUTTON AREA --- */}
{/* This View is a sibling of ScrollView, allowing it to float on top */}
{canStartGroup && ( {canStartGroup && (
<View style={styles.floatingButtonContainer}> <View style={styles.floatingButtonContainer}>
<TouchableOpacity <TouchableOpacity
@@ -390,10 +418,9 @@ const EditGroupScreen: React.FC<EditGroupScreenProps> = ({ route }) => {
); );
}; };
// --- Styles ---
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#F3F4F6' }, container: { flex: 1, backgroundColor: '#F3F4F6' },
// Added more paddingBottom to ensure content doesn't hide behind the button AND the tab bar
contentContainer: { padding: 16, paddingTop: 0, paddingBottom: 150 }, contentContainer: { padding: 16, paddingTop: 0, paddingBottom: 150 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' }, centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
errorText: { color: '#EF4444', fontSize: 16, textAlign: 'center' }, errorText: { color: '#EF4444', fontSize: 16, textAlign: 'center' },
@@ -437,15 +464,13 @@ const styles = StyleSheet.create({
cancelButton: { backgroundColor: '#E5E7EB' }, cancelButton: { backgroundColor: '#E5E7EB' },
inviteButton: { backgroundColor: colors.primary }, inviteButton: { backgroundColor: colors.primary },
buttonText: { color: '#ffffffff', fontSize: 15, fontWeight: '600' }, buttonText: { color: '#ffffffff', fontSize: 15, fontWeight: '600' },
// --- FLOATING BUTTON STYLES ---
floatingButtonContainer: { floatingButtonContainer: {
position: 'absolute', position: 'absolute',
bottom: 85, // Positioned above the tab bar bottom: 85,
left: 20, left: 20,
right: 20, right: 20,
alignItems: 'center', // Center the button horizontally alignItems: 'center',
zIndex: 1, // Ensure it's on top zIndex: 1,
}, },
activateButton: { activateButton: {
backgroundColor: colors.pastelOrange, backgroundColor: colors.pastelOrange,
@@ -457,7 +482,7 @@ const styles = StyleSheet.create({
shadowOffset: { width: 0, height: 4 }, shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3, shadowOpacity: 0.3,
shadowRadius: 4, shadowRadius: 4,
width: '90%', // Adjust width as needed width: '90%',
alignItems: 'center', alignItems: 'center',
}, },
activateButtonText: { activateButtonText: {
@@ -467,4 +492,5 @@ const styles = StyleSheet.create({
}, },
}); });
export default EditGroupScreen; export default EditGroupScreen;
+143 -180
View File
@@ -1,15 +1,15 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState, useCallback } from 'react';
import { View, Text, StyleSheet, ActivityIndicator, ScrollView, RefreshControl, Alert, TouchableOpacity } from 'react-native'; import { View, Text, StyleSheet, ActivityIndicator, ScrollView, RefreshControl, Alert, TouchableOpacity, Modal, TextInput } from 'react-native';
import { useRoute, useNavigation, RouteProp } from '@react-navigation/native'; import { useRoute, RouteProp } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { Ionicons } from "@react-native-vector-icons/ionicons"; import { Ionicons } from "@react-native-vector-icons/ionicons";
import colors from '../../components/colors'; import colors from '../../components/colors';
import AppView from '../../components/AppView'; import AppView from '../../components/AppView';
import { apiCall } from '../../utils/ApiHelper'; import { apiCall } from '../../utils/ApiHelper';
// --- TYPE DEFINITIONS --- // --- CONSTANTS ---
const API_BASE_URL = 'https://exotic-humpback-mistakenly.ngrok-free.app/api/payments';
// Generic API Response Wrapper // --- TYPE DEFINITIONS ---
interface ApiResponse<T> { interface ApiResponse<T> {
status: 'success' | 'error'; status: 'success' | 'error';
data: T; data: T;
@@ -17,69 +17,54 @@ interface ApiResponse<T> {
statusCode: number; statusCode: number;
} }
// Type for an individual participant in the payment details
interface Participant { interface Participant {
userId: string; userId: string;
userName: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
mobile: string; mobile: string;
contributionAmount: number; contributionAmount: number;
paidAmount: number; paidAmount: number;
payAmount?: number;
status: 'SUCCESS' | 'PENDING'; status: 'SUCCESS' | 'PENDING';
lateFee: number; lateFee: number;
daysLate: number;
lateFeeWaived: boolean; lateFeeWaived: boolean;
} }
// Fully typed payment details based on your JSON response
interface PaymentDetails { interface PaymentDetails {
groupId: string; groupId: string;
groupName: string; groupName: string;
currentCycle: number; currentCycle: number;
cycleType: string;
contributionAmount: number; contributionAmount: number;
totalContributedAmount: number;
lateFeeMode: string;
lateFeeValue: number;
notReceivedAmount: number;
totalLateFee: number;
totalDueWithLateFee: number; totalDueWithLateFee: number;
participants: Participant[]; participants: Participant[];
} }
// Assuming your navigation stack params look something like this type PaymentStackParamList = { Payments: { groupId: string } };
type PaymentStackParamList = {
Payments: { groupId: string };
};
type PaymentsRouteProp = RouteProp<PaymentStackParamList, 'Payments'>; type PaymentsRouteProp = RouteProp<PaymentStackParamList, 'Payments'>;
type PaymentsNavigationProp = StackNavigationProp<PaymentStackParamList>;
const Payments: React.FC = () => { const Payments: React.FC = () => {
const route = useRoute<PaymentsRouteProp>(); const route = useRoute<PaymentsRouteProp>();
const navigation = useNavigation<PaymentsNavigationProp>();
const { groupId } = route.params; const { groupId } = route.params;
const [paymentDetails, setPaymentDetails] = useState<PaymentDetails | null>(null); const [paymentDetails, setPaymentDetails] = useState<PaymentDetails | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
// State for the waiver modal
const [isModalVisible, setModalVisible] = useState(false);
const [currentParticipant, setCurrentParticipant] = useState<Participant | null>(null);
const [waiverAmount, setWaiverAmount] = useState('');
const fetchPaymentDetails = useCallback(async () => { const fetchPaymentDetails = useCallback(async () => {
if (!refreshing) setLoading(true); if (!refreshing) setLoading(true);
try { try {
const response = await apiCall( const response = await apiCall(`${API_BASE_URL}/current/payment/details/${groupId}`, 'GET') as ApiResponse<PaymentDetails>;
`https://exotic-humpback-mistakenly.ngrok-free.app/api/payments/current/payment/details/${groupId}`,
'GET'
) as ApiResponse<PaymentDetails>;
if (response && response.statusCode === 200) { if (response && response.statusCode === 200) {
setPaymentDetails(response.data); setPaymentDetails(response.data);
} else { } else {
Alert.alert("Error", response?.message || "Failed to fetch payment details."); Alert.alert("Error", response?.message || "Failed to fetch details.");
} }
} catch (error: any) { } catch (error: any) {
console.error("Fetch payment details error:", error);
Alert.alert("Error", error.message || "An unexpected error occurred."); Alert.alert("Error", error.message || "An unexpected error occurred.");
} finally { } finally {
setLoading(false); setLoading(false);
@@ -91,205 +76,183 @@ const Payments: React.FC = () => {
fetchPaymentDetails(); fetchPaymentDetails();
}, [fetchPaymentDetails]); }, [fetchPaymentDetails]);
const onRefresh = () => { const onRefresh = useCallback(() => {
setRefreshing(true); setRefreshing(true);
fetchPaymentDetails();
}, [fetchPaymentDetails]);
const openWaiverDialog = (participant: Participant) => {
setCurrentParticipant(participant);
setWaiverAmount('');
setModalVisible(true);
};
// --- UPDATED: handleWaiveFee now uses the correct API endpoint ---
const handleWaiveFee = async () => {
if (!currentParticipant || !paymentDetails) return;
const amountToWaive = parseFloat(waiverAmount);
if (isNaN(amountToWaive) || amountToWaive <= 0) {
Alert.alert("Invalid Input", "Please enter a waiver amount greater than zero.");
return;
}
if (amountToWaive > currentParticipant.lateFee) {
Alert.alert("Invalid Amount", `Waiver cannot exceed the current late fee of ₹${currentParticipant.lateFee}.`);
return;
}
try {
// Construct the exact URL as provided
const url = `${API_BASE_URL}/groups/${groupId}/cycles/${paymentDetails.currentCycle}/waive/${currentParticipant.userId}?amountToWaive=${amountToWaive}`;
const response = await apiCall(url, 'PUT', {}); // Body is empty as amount is in URL
if (response && response.statusCode === 200) {
Alert.alert("Success", `₹${amountToWaive} has been waived successfully.`);
setModalVisible(false);
onRefresh(); // Refresh data to reflect the change
} else {
throw new Error(response?.message || "Failed to apply waiver.");
}
} catch (error: any) {
Alert.alert("API Error", error.message);
}
}; };
// --- ENHANCED: renderParticipant now shows late fee details ---
const renderParticipant = (participant: Participant) => { const renderParticipant = (participant: Participant) => {
const isPending = participant.status === 'PENDING'; const isPending = participant.status === 'PENDING';
const canWaive = isPending && participant.lateFee > 0;
return ( return (
<View key={participant.userId} style={styles.participantCard}> <View key={participant.userId} style={styles.participantCard}>
<View style={styles.participantInfo}> <View style={styles.participantInfo}>
<Text style={styles.participantName}>{participant.firstName} {participant.lastName}</Text> <Text style={styles.participantName}>{participant.firstName} {participant.lastName}</Text>
<Text style={styles.participantMobile}>{participant.mobile}</Text> <Text style={styles.participantMobile}>{participant.mobile}</Text>
</View> </View>
<View style={styles.paymentStatus}> <View style={styles.paymentStatus}>
<Text style={[ <Text style={[styles.statusText, { color: isPending ? colors.warning : colors.success }]}>
styles.statusText,
{ color: isPending ? '#e0d626ff' : '#18b107ff' }
]}>
{participant.status} {participant.status}
</Text> </Text>
<Text style={styles.amountText}> <Text style={styles.amountText}>
{isPending ?`₹${participant.payAmount}`:`₹${participant.paidAmount}`} {isPending ? `₹${participant.payAmount || 0}` : `₹${participant.paidAmount}`}
</Text> </Text>
{isPending && participant.lateFee > 0 && (
{/* --- NEW: Conditionally render late payment stats --- */}
{isPending && participant.daysLate > 0 && (
<View style={styles.lateFeeContainer}>
<Text style={styles.lateFeeText}>Late Fee: ₹{participant.lateFee}</Text> <Text style={styles.lateFeeText}>Late Fee: ₹{participant.lateFee}</Text>
<Text style={styles.lateFeeText}>Days Late: {participant.daysLate}</Text> )}
{participant.lateFee === 0 && participant.lateFeeWaived && <Text style={styles.waivedText}>Fee Waived</Text>}
</View> </View>
<View style={styles.menuContainer}>
{canWaive && (
<TouchableOpacity onPress={() => openWaiverDialog(participant)}>
<Ionicons name="ellipsis-vertical" size={22} color="#6c757d" />
</TouchableOpacity>
)} )}
</View> </View>
</View> </View>
); );
}; };
if (loading && !refreshing) {
return <ActivityIndicator style={styles.centered} size="large" color={colors.primary} />;
}
if (!paymentDetails) {
return (
<View style={styles.centered}>
<Text>No payment details found.</Text>
<TouchableOpacity onPress={fetchPaymentDetails} style={styles.retryButton}>
<Text style={styles.retryButtonText}>Tap to retry</Text>
</TouchableOpacity>
</View>
);
}
return ( return (
<AppView paddingHorizontal={0}> <AppView paddingHorizontal={0}>
<Modal
transparent={true}
visible={isModalVisible}
animationType="fade"
onRequestClose={() => setModalVisible(false)}
>
<View style={styles.modalOverlay}>
<View style={styles.modalContainer}>
<Text style={styles.modalTitle}>Waive Late Fee</Text>
<Text style={styles.modalSubtitle}>
For {currentParticipant?.firstName}. Current late fee is ₹{currentParticipant?.lateFee}.
</Text>
<TextInput
style={styles.input}
keyboardType="numeric"
value={waiverAmount}
onChangeText={setWaiverAmount}
placeholder="Enter amount to waive"
/>
<View style={styles.modalButtonContainer}>
<TouchableOpacity style={[styles.modalButton, styles.cancelButton]} onPress={() => setModalVisible(false)}>
<Text style={styles.cancelButtonText}>Cancel</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.modalButton, styles.saveButton]} onPress={handleWaiveFee}>
<Text style={styles.saveButtonText}>Apply Waiver</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
<ScrollView <ScrollView
style={styles.container} style={styles.container}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={[colors.primary]} tintColor={colors.primary} />} refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
> >
{loading && !refreshing ? (
<ActivityIndicator style={{marginTop: 50}} size="large" color={colors.primary} />
) : (
<>
<View style={styles.header}> <View style={styles.header}>
<Text style={styles.headerTitle}>{paymentDetails.groupName}</Text> <Text style={styles.headerTitle}>{paymentDetails?.groupName}</Text>
</View> </View>
<View style={styles.summaryContainer}> <View style={styles.summaryContainer}>
<View style={styles.summaryBox}> <View style={styles.summaryBox}>
<Text style={styles.summaryLabel}>Current Cycle</Text> <Text style={styles.summaryLabel}>Cycle</Text>
<Text style={styles.summaryValue}>{paymentDetails.currentCycle}</Text> <Text style={styles.summaryValue}>{paymentDetails?.currentCycle}</Text>
</View> </View>
<View style={styles.summaryBox}> <View style={styles.summaryBox}>
<Text style={styles.summaryLabel}>Contribution</Text> <Text style={styles.summaryLabel}>Contribution</Text>
<Text style={styles.summaryValue}>₹{paymentDetails.contributionAmount}</Text> <Text style={styles.summaryValue}>₹{paymentDetails?.contributionAmount}</Text>
</View> </View>
<View style={styles.summaryBox}> <View style={styles.summaryBox}>
<Text style={styles.summaryLabel}>Total Due</Text> <Text style={styles.summaryLabel}>Total Due</Text>
<Text style={[styles.summaryValue, { color: colors.danger }]}>₹{paymentDetails.totalDueWithLateFee}</Text> <Text style={[styles.summaryValue, { color: colors.danger }]}>₹{paymentDetails?.totalDueWithLateFee}</Text>
</View> </View>
</View> </View>
<Text style={styles.sectionTitle}>Participants</Text> <Text style={styles.sectionTitle}>Participants</Text>
{paymentDetails.participants.map(renderParticipant)} {paymentDetails?.participants.map(renderParticipant)}
</>
)}
</ScrollView> </ScrollView>
</AppView> </AppView>
); );
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
centered: { container: { flex: 1, backgroundColor: '#f4f6f8' },
flex: 1, header: { padding: 20, paddingBottom: 15 },
justifyContent: 'center', headerTitle: { color: '#000000', fontSize: 24, fontWeight: 'bold' },
alignItems: 'center', summaryContainer: { flexDirection: 'row', justifyContent: 'space-around', padding: 20, backgroundColor: '#fff', marginHorizontal: 15, borderRadius: 12, elevation: 2 },
backgroundColor: '#f4f6f8', summaryBox: { alignItems: 'center' },
}, summaryLabel: { fontSize: 14, color: '#6c757d', marginBottom: 4 },
container: { summaryValue: { fontSize: 18, fontWeight: '700', color: colors.primary },
flex: 1, sectionTitle: { fontSize: 20, fontWeight: 'bold', marginLeft: 20, marginTop: 20, marginBottom: 10 },
backgroundColor: '#f4f6f8', participantCard: { backgroundColor: '#fff', marginHorizontal: 15, marginBottom: 10, borderRadius: 10, padding: 15, flexDirection: 'row', alignItems: 'center', elevation: 1 },
}, participantInfo: { flex: 1 },
header: { participantName: { fontSize: 16, fontWeight: '600' },
padding: 20, participantMobile: { fontSize: 14, color: '#6c757d', marginTop: 2 },
paddingBottom: 15, paymentStatus: { alignItems: 'flex-end', marginHorizontal: 10 },
}, statusText: { fontSize: 14, fontWeight: 'bold', textTransform: 'uppercase' },
headerTitle: { amountText: { fontSize: 14, color: '#495057', marginTop: 2 },
color: '#000000', lateFeeText: { fontSize: 13, color: colors.danger, fontWeight: '600', marginTop: 4 },
fontSize: 24, waivedText: { fontSize: 13, color: colors.success, fontWeight: '600', marginTop: 4 },
fontWeight: 'bold', menuContainer: { justifyContent: 'center', alignItems: 'center', minWidth: 25 },
}, modalOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'center', alignItems: 'center' },
summaryContainer: { modalContainer: { width: '85%', backgroundColor: 'white', borderRadius: 15, padding: 20, elevation: 10 },
flexDirection: 'row', modalTitle: { fontSize: 20, fontWeight: 'bold', color: '#333', marginBottom: 5 },
justifyContent: 'space-around', modalSubtitle: { fontSize: 14, color: '#666', marginBottom: 20 },
padding: 20, input: { width: '100%', borderWidth: 1, borderColor: '#ddd', borderRadius: 8, paddingHorizontal: 15, paddingVertical: 12, fontSize: 16, marginBottom: 20 },
backgroundColor: '#fff', modalButtonContainer: { flexDirection: 'row', justifyContent: 'flex-end' },
marginHorizontal: 15, modalButton: { paddingVertical: 10, paddingHorizontal: 20, borderRadius: 8, marginLeft: 10 },
borderRadius: 12, cancelButton: { backgroundColor: '#f0f0f0' },
elevation: 2, cancelButtonText: { color: '#333', fontWeight: 'bold' },
shadowColor: '#000', saveButton: { backgroundColor: colors.primary },
shadowOpacity: 0.05, saveButtonText: { color: 'white', fontWeight: 'bold' },
shadowRadius: 10,
},
summaryBox: {
alignItems: 'center',
},
summaryLabel: {
fontSize: 14,
color: '#6c757d',
marginBottom: 4,
},
summaryValue: {
fontSize: 18,
fontWeight: '700',
color: colors.primary,
},
sectionTitle: {
fontSize: 20,
fontWeight: 'bold',
marginLeft: 20,
marginTop: 20,
marginBottom: 10,
color: '#343a40',
},
participantCard: {
backgroundColor: '#fff',
marginHorizontal: 15,
marginBottom: 10,
borderRadius: 10,
padding: 15,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
elevation: 1,
},
participantInfo: {
flex: 1,
},
participantName: {
fontSize: 16,
fontWeight: '600',
color: '#212529',
},
participantMobile: {
fontSize: 14,
color: '#6c757d',
marginTop: 2,
},
paymentStatus: {
alignItems: 'flex-end',
},
statusText: {
fontSize: 14,
fontWeight: 'bold',
textTransform: 'uppercase',
},
amountText: {
fontSize: 14,
color: '#495057',
marginTop: 2,
},
// --- NEW STYLES ---
lateFeeContainer: {
marginTop: 8,
paddingTop: 5,
borderTopWidth: 1,
borderTopColor: '#e9ecef',
alignItems: 'flex-end',
},
lateFeeText: {
fontSize: 13,
color: colors.danger,
fontWeight: '600',
},
retryButton: {
marginTop: 20,
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius: 8,
backgroundColor: colors.primary,
},
retryButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
},
}); });
export default Payments; export default Payments;
@@ -0,0 +1,248 @@
import React, { useState, useEffect, useRef } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Animated, Easing, SafeAreaView, Dimensions, Alert } from 'react-native';
import { Confetti, ConfettiMethods } from 'react-native-fast-confetti';
import AppView from '../../components/AppView';
// --- PROPS & TYPE DEFINITIONS ---
export interface RandomWinnerScreenProps {
participants: Participant[];
excludedParticipantIds: string[];
}
export interface Participant {
id: string;
name: string;
}
// --- CONSTANTS ---
const { width } = Dimensions.get('window');
const ITEM_HEIGHT = 70; // Height of each name item
const ANIMATION_DURATION = 8000; // 8 seconds for suspense
const SHUFFLE_MULTIPLIER = 5; // Repeats the list for a longer scroll effect
const RandomSelectUserScreen: React.FC = () => {
// --- MOCK DATA (Replace with navigation props) ---
const mockProps: RandomWinnerScreenProps = {
participants: [
{ id: '1', name: 'Vinay M.' },
{ id: '2', name: 'Nitesh Y.' },
{ id: '3', name: 'Alice' },
{ id: '4', name: 'Bob' },
{ id: '5', name: 'Charlie' },
{ id: '6', name: 'David' },
{ id: '7', name: 'Eve' },
],
excludedParticipantIds: ['4'], // Exclude Bob
};
// --- END MOCK DATA ---
const { participants, excludedParticipantIds } = mockProps;
const [eligibleParticipants, setEligibleParticipants] = useState<Participant[]>([]);
const [isSelecting, setIsSelecting] = useState(false);
const [winner, setWinner] = useState<Participant | null>(null);
const [animationList, setAnimationList] = useState<Participant[]>([]);
const scrollY = useRef(new Animated.Value(0)).current;
const confettiRef = useRef<ConfettiMethods>(null);
// Filter participants
useEffect(() => {
const eligible = participants.filter(p => !excludedParticipantIds.includes(p.id));
setEligibleParticipants(eligible);
}, [participants, excludedParticipantIds]);
const handleStartSelection = () => {
if (eligibleParticipants.length < 2) {
Alert.alert('Not enough eligible participants.');
return;
}
setIsSelecting(true);
setWinner(null);
scrollY.setValue(0);
// 1. Secretly select winner
const winnerIndex = Math.floor(Math.random() * eligibleParticipants.length);
const selectedWinner = eligibleParticipants[winnerIndex];
// 2. Create long, shuffled list for animation
let listForAnimation: Participant[] = [];
for (let i = 0; i < SHUFFLE_MULTIPLIER; i++) {
const shuffled = [...eligibleParticipants].sort(() => Math.random() - 0.5);
listForAnimation.push(...shuffled);
}
// 3. Place winner at the end
listForAnimation.push(selectedWinner);
setAnimationList(listForAnimation);
// 4. Calculate final scroll position
const finalY = -(listForAnimation.length - 1) * ITEM_HEIGHT;
// 5. Start animation
Animated.timing(scrollY, {
toValue: finalY,
duration: ANIMATION_DURATION,
useNativeDriver: true,
easing: Easing.bezier(0.3, 0, 0.1, 1),
}).start(() => {
setWinner(selectedWinner);
confettiRef.current?.start();
});
};
const handleReset = () => {
setIsSelecting(false);
setWinner(null);
scrollY.setValue(0);
confettiRef.current?.stop();
};
const renderListItem = ({ item, index }: { item: Participant, index: number }) => (
<View style={styles.itemContainer} key={`${item.id}-${index}`}>
<Text style={styles.itemName}>{item.name}</Text>
</View>
);
return (
<AppView>
<Confetti ref={confettiRef} />
<Text style={styles.title}>Select a Winner</Text>
<View style={styles.selectionMachine}>
<View style={styles.winnerPointer} />
{isSelecting || winner ? (
<Animated.View style={{ transform: [{ translateY: scrollY }] }}>
{animationList.map((item, index) => renderListItem({ item, index }))}
</Animated.View>
) : (
<View style={styles.placeholderContainer}>
<Text style={styles.placeholderText}>Press Start to Select</Text>
</View>
)}
</View>
{winner && (
<View style={styles.winnerBanner}>
<Text style={styles.winnerText}>Winner is</Text>
<Text style={styles.winnerName}>{winner.name}!</Text>
</View>
)}
{!isSelecting && !winner && (
<TouchableOpacity style={styles.startButton} onPress={handleStartSelection}>
<Text style={styles.startButtonText}>Start Selection</Text>
</TouchableOpacity>
)}
{winner && (
<TouchableOpacity style={styles.resetButton} onPress={handleReset}>
<Text style={styles.resetButtonText}>Select Again</Text>
</TouchableOpacity>
)}
</AppView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#1C1C1E',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 40,
},
title: {
fontSize: 32,
fontWeight: 'bold',
color: '#FFFFFF',
textAlign: 'center',
marginBottom: 30,
},
selectionMachine: {
height: ITEM_HEIGHT,
width: '90%',
backgroundColor: '#2C2C2E',
borderRadius: 20,
overflow: 'hidden',
justifyContent: 'center',
},
winnerPointer: {
position: 'absolute',
width: '105%',
height: ITEM_HEIGHT,
borderRadius: 20,
borderWidth: 3,
borderColor: '#FBC02D',
alignSelf: 'center',
zIndex: 1,
},
itemContainer: {
height: ITEM_HEIGHT,
justifyContent: 'center',
alignItems: 'center',
},
itemName: {
fontSize: 36,
fontWeight: 'bold',
color: '#FFF',
},
placeholderContainer: {
height: ITEM_HEIGHT,
justifyContent: 'center',
alignItems: 'center',
},
placeholderText: {
fontSize: 24,
color: '#666',
fontStyle: 'italic',
},
winnerBanner: {
marginTop: 30,
padding: 20,
backgroundColor: '#FBC02D',
borderRadius: 15,
alignItems: 'center',
},
winnerText: {
fontSize: 20,
color: '#000',
},
winnerName: {
fontSize: 40,
fontWeight: 'bold',
color: '#000',
},
startButton: {
backgroundColor: '#007AFF',
paddingVertical: 18,
paddingHorizontal: 40,
borderRadius: 30,
marginTop: 40,
},
startButtonText: {
color: '#FFFFFF',
fontSize: 20,
fontWeight: 'bold',
},
resetButton: {
backgroundColor: '#333',
paddingVertical: 18,
paddingHorizontal: 40,
borderRadius: 30,
marginTop: 40,
},
resetButtonText: {
color: '#FFFFFF',
fontSize: 20,
fontWeight: 'bold',
},
});
export default RandomSelectUserScreen;