diff --git a/formulus/__tests__/App.test.tsx b/formulus/__tests__/App.test.tsx index 9b31e9e4f..658b85e3d 100644 --- a/formulus/__tests__/App.test.tsx +++ b/formulus/__tests__/App.test.tsx @@ -4,11 +4,11 @@ import React from 'react'; import {render} from '@testing-library/react-native'; +import {jest, describe, test, expect} from '@jest/globals'; // Mock the App component instead of trying to render the real one // This avoids issues with native modules and database initialization jest.mock('../App', () => { - const React = require('react'); const {View} = require('react-native'); return function MockedApp() { return ; diff --git a/formulus/assets/images/logo.png b/formulus/assets/images/logo.png index a5994bc28..3d1c74bd7 100644 Binary files a/formulus/assets/images/logo.png and b/formulus/assets/images/logo.png differ diff --git a/formulus/package-lock.json b/formulus/package-lock.json index 8c83429f2..0aed3e734 100644 --- a/formulus/package-lock.json +++ b/formulus/package-lock.json @@ -54,6 +54,7 @@ "@types/react": "^19.0.0", "@types/react-native-vector-icons": "^6.4.18", "@types/react-test-renderer": "^19.0.0", + "baseline-browser-mapping": "^2.9.11", "eslint": "^8.19.0", "eslint-config-prettier": "8.10.2", "jest": "^29.7.0", @@ -2334,109 +2335,6 @@ "node": "20 || >=22" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@isaacs/ttlcache": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", @@ -2485,9 +2383,9 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -3030,15 +2928,15 @@ } }, "node_modules/@nestjs/common": { - "version": "11.1.6", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.6.tgz", - "integrity": "sha512-krKwLLcFmeuKDqngG2N/RuZHCs2ycsKcxWIDgcm7i1lf3sQ0iG03ci+DsP/r3FcT/eJDFsIHnKtNta2LIi7PzQ==", + "version": "11.1.11", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.11.tgz", + "integrity": "sha512-R/+A8XFqLgN8zNs2twhrOaE7dJbRQhdPX3g46am4RT/x8xGLqDphrXkUIno4cGUZHxbczChBAaAPTdPv73wDZA==", "dev": true, "license": "MIT", "dependencies": { - "file-type": "21.0.0", + "file-type": "21.2.0", "iterare": "1.2.1", - "load-esm": "1.0.2", + "load-esm": "1.0.3", "tslib": "2.8.1", "uid": "2.0.2" }, @@ -3062,9 +2960,9 @@ } }, "node_modules/@nestjs/core": { - "version": "11.1.6", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.6.tgz", - "integrity": "sha512-siWX7UDgErisW18VTeJA+x+/tpNZrJewjTBsRPF3JVxuWRuAB1kRoiJcxHgln8Lb5UY9NdvklITR84DUEXD0Cg==", + "version": "11.1.11", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.11.tgz", + "integrity": "sha512-H9i+zT3RvHi7tDc+lCmWHJ3ustXveABCr+Vcpl96dNOxgmrx4elQSTC4W93Mlav2opfLV+p0UTHY6L+bpUA4zA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3072,7 +2970,7 @@ "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", - "path-to-regexp": "8.2.0", + "path-to-regexp": "8.3.0", "tslib": "2.8.1", "uid": "2.0.2" }, @@ -3251,25 +3149,25 @@ "license": "MIT" }, "node_modules/@openapitools/openapi-generator-cli": { - "version": "2.23.1", - "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.23.1.tgz", - "integrity": "sha512-Kd5EZqzbcIXf6KRlpUrheHMzQNRHsJWzAGrm4ncWCNhnQl+Mh6TsFcqq+hIetgiFCknWBH6cZ2f37SxPxaon4w==", + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.27.0.tgz", + "integrity": "sha512-JJWO9joe6TudGdHuwBi+NhEMIwXcz1B2FawBsJ5SjgTMQxSjArkChq+ro6Ovv6q1zfWavCs/q9uYeNPtRRcQBA==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@nestjs/axios": "4.0.1", - "@nestjs/common": "11.1.6", - "@nestjs/core": "11.1.6", + "@nestjs/common": "11.1.11", + "@nestjs/core": "11.1.11", "@nuxtjs/opencollective": "0.3.2", - "axios": "1.11.0", + "axios": "1.13.2", "chalk": "4.1.2", "commander": "8.3.0", - "compare-versions": "4.1.4", + "compare-versions": "6.1.1", "concurrently": "9.2.1", "console.table": "0.10.0", - "fs-extra": "11.3.1", - "glob": "11.0.3", + "fs-extra": "11.3.3", + "glob": "13.0.0", "inquirer": "8.2.7", "proxy-agent": "6.5.0", "reflect-metadata": "0.2.2", @@ -4199,15 +4097,14 @@ "license": "MIT" }, "node_modules/@tokenizer/inflate": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", - "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.4.0", - "fflate": "^0.8.2", - "token-types": "^6.0.0" + "debug": "^4.4.3", + "token-types": "^6.1.1" }, "engines": { "node": ">=18" @@ -5164,9 +5061,9 @@ } }, "node_modules/axios": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", - "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "dev": true, "license": "MIT", "dependencies": { @@ -5378,9 +5275,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.3.tgz", - "integrity": "sha512-mcE+Wr2CAhHNWxXN/DdTI+n4gsPc5QpXpWnyCQWiQYIYZX+ZMJ8juXZgjRa/0/YPJo/NSsgW15/YgmI4nbysYw==", + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -5409,24 +5306,24 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "devOptional": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -5443,6 +5340,27 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -5450,6 +5368,16 @@ "devOptional": true, "license": "MIT" }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -5961,9 +5889,9 @@ } }, "node_modules/compare-versions": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-4.1.4.tgz", - "integrity": "sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", "dev": true, "license": "MIT" }, @@ -6527,13 +6455,6 @@ "node": ">= 0.4" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, "node_modules/easy-table": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz", @@ -7674,13 +7595,6 @@ "bser": "2.1.1" } }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "dev": true, - "license": "MIT" - }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -7721,15 +7635,15 @@ } }, "node_modules/file-type": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.0.0.tgz", - "integrity": "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==", + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.2.0.tgz", + "integrity": "sha512-vCYBgFOrJQLoTzDyAXAL/RFfKnXXpUYt4+tipVy26nJJhT7ftgGETf2tAQF59EEL61i3MrorV/PG6tf7LJK7eg==", "dev": true, "license": "MIT", "dependencies": { - "@tokenizer/inflate": "^0.2.7", - "strtok3": "^10.2.2", - "token-types": "^6.0.0", + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" }, "engines": { @@ -7887,36 +7801,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", @@ -7944,9 +7828,9 @@ } }, "node_modules/fs-extra": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", - "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", "dev": true, "license": "MIT", "dependencies": { @@ -8132,22 +8016,16 @@ } }, "node_modules/glob": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", - "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.0.3", + "minimatch": "^10.1.1", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, "engines": { "node": "20 || >=22" }, @@ -8169,11 +8047,11 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/brace-expansion": "^5.0.0" }, @@ -9277,22 +9155,6 @@ "node": ">= 0.4" } }, - "node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -10392,9 +10254,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -10584,9 +10446,9 @@ "license": "MIT" }, "node_modules/load-esm": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.2.tgz", - "integrity": "sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz", + "integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==", "dev": true, "funding": [ { @@ -11091,9 +10953,9 @@ } }, "node_modules/metro-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -11919,13 +11781,6 @@ "node": ">= 14" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -12003,9 +11858,9 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -12020,23 +11875,24 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz", - "integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=16" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/path-type": { @@ -12335,13 +12191,13 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "devOptional": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -12408,21 +12264,52 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "devOptional": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/react": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", @@ -13988,32 +13875,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/string-width/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -14133,20 +13994,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -15089,25 +14936,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/formulus/package.json b/formulus/package.json index 82897e503..1d8c8aaad 100644 --- a/formulus/package.json +++ b/formulus/package.json @@ -63,9 +63,10 @@ "@types/react": "^19.0.0", "@types/react-native-vector-icons": "^6.4.18", "@types/react-test-renderer": "^19.0.0", + "baseline-browser-mapping": "^2.9.11", "eslint": "^8.19.0", - "jest": "^29.7.0", "eslint-config-prettier": "8.10.2", + "jest": "^29.7.0", "prettier": "2.8.8", "react-test-renderer": "19.0.0", "ts-node": "^10.9.2", diff --git a/formulus/src/api/synkronus/Auth.ts b/formulus/src/api/synkronus/Auth.ts index add1da2e7..ea2ab9013 100644 --- a/formulus/src/api/synkronus/Auth.ts +++ b/formulus/src/api/synkronus/Auth.ts @@ -8,13 +8,49 @@ export interface UserInfo { role: UserRole; } +const decodeBase64 = (input: string): string => { + const atobFn = (globalThis as any).atob as + | ((data: string) => string) + | undefined; + if (typeof atobFn === 'function') { + return atobFn(input); + } + + const chars = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + let str = ''; + let i = 0; + + // Basic base64 decoder fallback + while (i < input.length) { + const enc1 = chars.indexOf(input.charAt(i++)); + const enc2 = chars.indexOf(input.charAt(i++)); + const enc3 = chars.indexOf(input.charAt(i++)); + const enc4 = chars.indexOf(input.charAt(i++)); + + const chr1 = enc1 * 4 + Math.floor(enc2 / 16); + const chr2 = (enc2 % 16) * 16 + Math.floor(enc3 / 4); + const chr3 = (enc3 % 4) * 64 + enc4; + + str += String.fromCharCode(chr1); + if (enc3 !== 64) { + str += String.fromCharCode(chr2); + } + if (enc4 !== 64) { + str += String.fromCharCode(chr3); + } + } + + return str; +}; + // Decode JWT payload without verification (claims are in the middle part) function decodeJwtPayload(token: string): any { try { const parts = token.split('.'); if (parts.length !== 3) return null; const payload = parts[1]; - const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/')); + const decoded = decodeBase64(payload.replace(/-/g, '+').replace(/_/g, '/')); return JSON.parse(decoded); } catch { return null; @@ -34,10 +70,10 @@ export const login = async ( loginRequest: {username, password}, }); - const {token, refreshToken, expiresAt} = res.data; + const {token, refreshToken: refreshTokenValue, expiresAt} = res.data; await AsyncStorage.setItem('@token', token); - await AsyncStorage.setItem('@refreshToken', refreshToken); + await AsyncStorage.setItem('@refreshToken', refreshTokenValue); await AsyncStorage.setItem('@tokenExpiresAt', expiresAt.toString()); // Decode JWT to get user info @@ -100,9 +136,9 @@ export const refreshToken = async () => { refreshToken: (await AsyncStorage.getItem('@refreshToken')) ?? '', }, }); - const {token, refreshToken, expiresAt} = res.data; + const {token, refreshToken: refreshTokenValue, expiresAt} = res.data; await AsyncStorage.setItem('@token', token); - await AsyncStorage.setItem('@refreshToken', refreshToken); + await AsyncStorage.setItem('@refreshToken', refreshTokenValue); await AsyncStorage.setItem('@tokenExpiresAt', expiresAt.toString()); return true; }; diff --git a/formulus/src/components/CustomAppWebView.tsx b/formulus/src/components/CustomAppWebView.tsx index ef4775307..a825af986 100644 --- a/formulus/src/components/CustomAppWebView.tsx +++ b/formulus/src/components/CustomAppWebView.tsx @@ -6,7 +6,7 @@ import React, { useImperativeHandle, useMemo, } from 'react'; -import {View, ActivityIndicator, AppState} from 'react-native'; +import {View, ActivityIndicator, AppState, StyleSheet} from 'react-native'; import {WebView} from 'react-native-webview'; import {useIsFocused} from '@react-navigation/native'; import {Platform} from 'react-native'; @@ -249,7 +249,7 @@ const CustomAppWebView = forwardRef< if (!isScriptReady) { return ( - + ); @@ -308,7 +308,7 @@ const CustomAppWebView = forwardRef< startInLoadingState={true} originWhitelist={['*']} renderLoading={() => ( - + )} @@ -316,4 +316,12 @@ const CustomAppWebView = forwardRef< ); }); +const styles = StyleSheet.create({ + centered: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, +}); + export default CustomAppWebView; diff --git a/formulus/src/components/FormplayerModal.tsx b/formulus/src/components/FormplayerModal.tsx index e6a7e37f3..e0f93ed7f 100644 --- a/formulus/src/components/FormplayerModal.tsx +++ b/formulus/src/components/FormplayerModal.tsx @@ -405,7 +405,7 @@ const FormplayerModal = forwardRef( {currentObservationId ? 'Edit Observation' : 'New Observation'} - + = ({ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( /[xy]/g, function (c) { - const r = (Math.random() * 16) | 0; - const v = c === 'x' ? r : (r & 0x3) | 0x8; + const r = Math.floor(Math.random() * 16); + const v = c === 'x' ? r : (r % 4) + 8; return v.toString(16); }, ); diff --git a/formulus/src/database/repositories/__tests__/WatermelonDBRepo.test.ts b/formulus/src/database/repositories/__tests__/WatermelonDBRepo.test.ts index d83d9d098..6b1e2d38a 100644 --- a/formulus/src/database/repositories/__tests__/WatermelonDBRepo.test.ts +++ b/formulus/src/database/repositories/__tests__/WatermelonDBRepo.test.ts @@ -507,88 +507,5 @@ describe('WatermelonDBRepo', () => { } }); - // Test the synchronize method - test.skip('synchronize should pull and push observations correctly', async () => { - // Arrange - create some local observations that need syncing - const localObservation1: Partial = { - formType: 'sync-test-local-1', - data: {source: 'local', value: 1}, - }; - - const localObservation2: Partial = { - formType: 'sync-test-local-2', - data: {source: 'local', value: 2}, - }; - - // Save the local observations - const localId1 = await repo.saveObservation(localObservation1); - const localId2 = await repo.saveObservation(localObservation2); - - // Verify local observations were saved - const collection = database.get('observations'); - const initialCount = await collection.query().fetchCount(); - expect(initialCount).toBe(2); - - // Create mock server data to be pulled - const serverObservations = [ - { - observationId: 'server-obs-1', - formType: 'sync-test-server-1', - formVersion: '1.0', - data: {source: 'server', value: 1}, - deleted: false, - }, - { - observationId: 'server-obs-2', - formType: 'sync-test-server-2', - formVersion: '1.0', - data: {source: 'server', value: 2}, - deleted: false, - }, - ]; - - // Create mock functions for pull and push - const mockPullChanges = jest.fn().mockResolvedValue(serverObservations); - - const pushedObservations: Observation[] = []; - const mockPushChanges = jest - .fn() - .mockImplementation((observations: Observation[]) => { - pushedObservations.push(...observations); - return Promise.resolve(); - }); - - // Act - perform synchronization - await repo.synchronize(mockPullChanges, mockPushChanges); - - // Assert - verify pull was called - expect(mockPullChanges).toHaveBeenCalledTimes(1); - - // Verify server observations were saved locally - const afterSyncCount = await collection.query().fetchCount(); - expect(afterSyncCount).toBe(4); // 2 local + 2 server - - // Verify we can retrieve the server observations - const serverObs1 = await repo.getObservation('server-obs-1'); - const serverObs2 = await repo.getObservation('server-obs-2'); - - expect(serverObs1).not.toBeNull(); - expect(serverObs2).not.toBeNull(); - - if (serverObs1 && serverObs2) { - expect(serverObs1.formType).toBe('sync-test-server-1'); - expect(serverObs2.formType).toBe('sync-test-server-2'); - } - - // Verify push was called with local observations - expect(mockPushChanges).toHaveBeenCalledTimes(1); - expect(pushedObservations.length).toBe(2); - - // Verify local observations were marked as synced - const syncedObs1 = await repo.getObservation(localId1); - const syncedObs2 = await repo.getObservation(localId2); - - expect(syncedObs1?.syncedAt).toBeTruthy(); - expect(syncedObs2?.syncedAt).toBeTruthy(); - }); + test.todo('synchronize should pull and push observations correctly'); }); diff --git a/formulus/src/navigation/MainAppNavigator.tsx b/formulus/src/navigation/MainAppNavigator.tsx index f5994d07d..06260bd9e 100644 --- a/formulus/src/navigation/MainAppNavigator.tsx +++ b/formulus/src/navigation/MainAppNavigator.tsx @@ -3,7 +3,6 @@ import {createStackNavigator} from '@react-navigation/stack'; import {useFocusEffect} from '@react-navigation/native'; import MainTabNavigator from './MainTabNavigator'; import WelcomeScreen from '../screens/WelcomeScreen'; -import SettingsScreen from '../screens/SettingsScreen'; import FormManagementScreen from '../screens/FormManagementScreen'; import ObservationDetailScreen from '../screens/ObservationDetailScreen'; import {MainAppStackParamList} from '../types/NavigationTypes'; @@ -51,11 +50,6 @@ const MainAppNavigator: React.FC = () => { component={MainTabNavigator} options={{headerShown: false}} /> - (); + +type TabBarIconProps = { + color: string; + size: number; +}; + +const renderHomeIcon = ({color, size}: TabBarIconProps) => ( + +); + +const renderFormsIcon = ({color, size}: TabBarIconProps) => ( + +); + +const renderObservationsIcon = ({color, size}: TabBarIconProps) => ( + +); + +const renderSyncIcon = ({color, size}: TabBarIconProps) => ( + +); + +const renderMoreIcon = ({color, size}: TabBarIconProps) => ( + +); const MainTabNavigator: React.FC = () => { const insets = useSafeAreaInsets(); @@ -34,47 +63,58 @@ const MainTabNavigator: React.FC = () => { name="Home" component={HomeScreen} options={{ - tabBarIcon: ({color, size}) => ( - - ), + tabBarIcon: renderHomeIcon, }} /> ( - - ), + tabBarIcon: renderFormsIcon, }} /> ( - - ), + tabBarIcon: renderObservationsIcon, }} /> ( - - ), + tabBarIcon: renderSyncIcon, + }} + /> + null, + }} + /> + null, + }} + /> + null, }} /> ( - - ), + tabBarIcon: renderMoreIcon, }} - listeners={({navigation, _route}) => ({ + listeners={({navigation}) => ({ tabPress: _e => { const state = navigation.getState(); const currentRoute = state.routes[state.index]; diff --git a/formulus/src/screens/AboutScreen.tsx b/formulus/src/screens/AboutScreen.tsx new file mode 100644 index 000000000..21ddc16e1 --- /dev/null +++ b/formulus/src/screens/AboutScreen.tsx @@ -0,0 +1,155 @@ +import React, {useEffect, useState} from 'react'; +import {View, Text, StyleSheet, Image, ScrollView, Linking} from 'react-native'; +import {SafeAreaView} from 'react-native-safe-area-context'; +import colors from '../theme/colors'; +import {appVersionService} from '../services/AppVersionService'; + +const AboutScreen: React.FC = () => { + const [version, setVersion] = useState(''); + + useEffect(() => { + const loadVersion = async () => { + try { + const v = await appVersionService.getFullVersion(); + setVersion(v); + } catch (_e) { + setVersion(''); + } + }; + + loadVersion(); + }, []); + + return ( + + + About + Information about this app + + + + + + + ODE + {!!version && v{version}} + + + + + Formulus + + Formulus is the mobile app for collecting and synchronizing forms + and observations. + + + + + Support + + If you need help, contact your system administrator. + + + + + Free & Open Source + + This application is free and open source software. We would love to + hear your feedback and welcome contributions. + + + Linking.openURL('https://forum.opendataensemble.org') + } + suppressHighlighting={true}> + https://forum.opendataensemble.org + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.neutral[50], + }, + header: { + padding: 16, + backgroundColor: colors.neutral.white, + borderBottomWidth: 1, + borderBottomColor: colors.neutral[200], + }, + title: { + fontSize: 28, + fontWeight: 'bold', + color: colors.neutral[900], + marginBottom: 4, + }, + subtitle: { + fontSize: 14, + color: colors.neutral[600], + }, + content: { + padding: 16, + paddingBottom: 32, + }, + brandRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + marginBottom: 16, + }, + logo: { + width: 56, + height: 56, + }, + brandText: { + flex: 1, + }, + appName: { + fontSize: 20, + fontWeight: '700', + color: colors.neutral[900], + }, + version: { + marginTop: 2, + fontSize: 12, + color: colors.neutral[600], + }, + card: { + backgroundColor: colors.neutral.white, + borderRadius: 12, + padding: 16, + marginBottom: 12, + shadowColor: colors.neutral.black, + shadowOffset: {width: 0, height: 2}, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 3, + }, + cardTitle: { + fontSize: 16, + fontWeight: '600', + color: colors.neutral[900], + marginBottom: 8, + }, + cardText: { + fontSize: 14, + color: colors.neutral[600], + lineHeight: 20, + }, + link: { + color: colors.brand.primary[500], + marginTop: 12, + fontWeight: '600', + }, +}); + +export default AboutScreen; diff --git a/formulus/src/screens/FormsScreen.tsx b/formulus/src/screens/FormsScreen.tsx index 44a6c6038..4d3988f39 100644 --- a/formulus/src/screens/FormsScreen.tsx +++ b/formulus/src/screens/FormsScreen.tsx @@ -43,8 +43,8 @@ const FormsScreen: React.FC = () => { ) { await refresh(); } - } catch (error) { - console.error('Error opening form:', error); + } catch (err) { + console.error('Error opening form:', err); Alert.alert('Error', 'Failed to open form. Please try again.'); } }; diff --git a/formulus/src/screens/HelpScreen.tsx b/formulus/src/screens/HelpScreen.tsx new file mode 100644 index 000000000..6eff5e54d --- /dev/null +++ b/formulus/src/screens/HelpScreen.tsx @@ -0,0 +1,112 @@ +import React from 'react'; +import {View, Text, StyleSheet, ScrollView, Linking} from 'react-native'; +import {SafeAreaView} from 'react-native-safe-area-context'; +import colors from '../theme/colors'; + +const HelpScreen: React.FC = () => { + return ( + + + Help & Support + Get help and share feedback + + + + + Community Forum + + We would love to hear your feedback and welcome contributions. + + + Linking.openURL('https://forum.opendataensemble.org') + } + suppressHighlighting={true}> + https://forum.opendataensemble.org + + + + + Troubleshooting + + If something is not working as expected: + + + 1. Check your internet connection. + + + 2. Try syncing again from the Sync tab. + + + 3. If the issue persists, reach out via the forum. + + + + + Administrator + + For account setup, server configuration, or access issues, contact + your system administrator. + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.neutral[50], + }, + header: { + padding: 16, + backgroundColor: colors.neutral.white, + borderBottomWidth: 1, + borderBottomColor: colors.neutral[200], + }, + title: { + fontSize: 28, + fontWeight: 'bold', + color: colors.neutral[900], + marginBottom: 4, + }, + subtitle: { + fontSize: 14, + color: colors.neutral[600], + }, + content: { + padding: 16, + paddingBottom: 32, + }, + card: { + backgroundColor: colors.neutral.white, + borderRadius: 12, + padding: 16, + marginBottom: 12, + shadowColor: colors.neutral.black, + shadowOffset: {width: 0, height: 2}, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 3, + }, + cardTitle: { + fontSize: 16, + fontWeight: '600', + color: colors.neutral[900], + marginBottom: 8, + }, + cardText: { + fontSize: 14, + color: colors.neutral[600], + lineHeight: 20, + }, + link: { + color: colors.brand.primary[500], + marginTop: 12, + fontWeight: '600', + }, +}); + +export default HelpScreen; diff --git a/formulus/src/screens/MoreScreen.tsx b/formulus/src/screens/MoreScreen.tsx index 9e722ec6d..55954e5a0 100644 --- a/formulus/src/screens/MoreScreen.tsx +++ b/formulus/src/screens/MoreScreen.tsx @@ -16,6 +16,14 @@ import { import MenuDrawer from '../components/MenuDrawer'; import {logout} from '../api/synkronus/Auth'; +type MainAppDrawerScreen = 'FormManagement'; + +const isMainAppDrawerScreen = ( + screen: string, +): screen is MainAppDrawerScreen => { + return screen === 'FormManagement'; +}; + type MoreScreenNavigationProp = CompositeNavigationProp< BottomTabNavigationProp, StackNavigationProp @@ -42,8 +50,14 @@ const MoreScreen: React.FC = () => { const handleNavigate = (screen: string) => { setDrawerVisible(false); // Navigate to screens in the MainAppStack - if (screen === 'Settings' || screen === 'FormManagement') { - navigation.navigate(screen as keyof MainAppStackParamList); + if (isMainAppDrawerScreen(screen)) { + navigation.navigate(screen); + } else if (screen === 'Settings') { + navigation.navigate('Settings'); + } else if (screen === 'About') { + navigation.navigate('About'); + } else if (screen === 'Help') { + navigation.navigate('Help'); } else { // Other screens not yet implemented - stay on Home for now console.log('Navigate to:', screen, '(not yet implemented)'); diff --git a/formulus/src/screens/ObservationsScreen.tsx b/formulus/src/screens/ObservationsScreen.tsx index ae0fbafaf..67a9eb453 100644 --- a/formulus/src/screens/ObservationsScreen.tsx +++ b/formulus/src/screens/ObservationsScreen.tsx @@ -124,8 +124,8 @@ const ObservationsScreen: React.FC = () => { ) { await refresh(); } - } catch (error) { - console.error('Error editing observation:', error); + } catch (err) { + console.error('Error editing observation:', err); Alert.alert('Error', 'Failed to edit observation. Please try again.'); } }; @@ -144,8 +144,8 @@ const ObservationsScreen: React.FC = () => { const formService = await FormService.getInstance(); await formService.deleteObservation(observation.observationId); await refresh(); - } catch (error) { - console.error('Error deleting observation:', error); + } catch (err) { + console.error('Error deleting observation:', err); Alert.alert('Error', 'Failed to delete observation.'); } }, diff --git a/formulus/src/screens/SettingsScreen.tsx b/formulus/src/screens/SettingsScreen.tsx index eff272af1..1b6647e75 100644 --- a/formulus/src/screens/SettingsScreen.tsx +++ b/formulus/src/screens/SettingsScreen.tsx @@ -9,24 +9,25 @@ import { Image, ScrollView, Alert, + type AlertButton, } from 'react-native'; import {SafeAreaView} from 'react-native-safe-area-context'; import {useNavigation} from '@react-navigation/native'; -import {StackNavigationProp} from '@react-navigation/stack'; +import {BottomTabNavigationProp} from '@react-navigation/bottom-tabs'; import * as Keychain from 'react-native-keychain'; import {login, getUserInfo, UserInfo} from '../api/synkronus/Auth'; import {serverConfigService} from '../services/ServerConfigService'; import QRScannerModal from '../components/QRScannerModal'; import {QRSettingsService} from '../services/QRSettingsService'; -import {MainAppStackParamList} from '../types/NavigationTypes'; +import {MainTabParamList} from '../types/NavigationTypes'; import {colors} from '../theme/colors'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import {ToastService} from '../services/ToastService'; import {serverSwitchService} from '../services/ServerSwitchService'; import {syncService} from '../services/SyncService'; -type SettingsScreenNavigationProp = StackNavigationProp< - MainAppStackParamList, +type SettingsScreenNavigationProp = BottomTabNavigationProp< + MainTabParamList, 'Settings' >; @@ -92,7 +93,7 @@ const SettingsScreen = () => { ? `Unsynced observations: ${pendingObservations}\nUnsynced attachments: ${pendingAttachments}\n\nSync is recommended before switching.` : 'Switching servers will wipe all local data for the previous server.'; - const buttons = hasPending + const buttons: AlertButton[] = hasPending ? [ { text: 'Cancel', @@ -213,7 +214,7 @@ const SettingsScreen = () => { const userInfo = await login(username, password); setLoggedInUser(userInfo); ToastService.showShort('Successfully logged in!'); - navigation.navigate('MainApp'); + navigation.navigate('Home'); } catch (error: any) { console.error('Login failed:', error); const errorMessage = @@ -257,7 +258,7 @@ const SettingsScreen = () => { const userInfo = await login(settings.username, settings.password); setLoggedInUser(userInfo); ToastService.showShort('Successfully logged in!'); - navigation.navigate('MainApp'); + navigation.navigate('Home'); } catch (error: any) { console.error('Auto-login failed:', error); const errorMessage = diff --git a/formulus/src/types/NavigationTypes.ts b/formulus/src/types/NavigationTypes.ts index edf991768..56a2a98f9 100644 --- a/formulus/src/types/NavigationTypes.ts +++ b/formulus/src/types/NavigationTypes.ts @@ -3,13 +3,15 @@ export type MainTabParamList = { Forms: undefined; Observations: undefined; Sync: undefined; + Settings: undefined; + About: undefined; + Help: undefined; More: {openDrawer?: number} | undefined; }; export type MainAppStackParamList = { Welcome: undefined; MainApp: undefined; - Settings: undefined; FormManagement: undefined; ObservationDetail: {observationId: string}; }; diff --git a/formulus/src/webview/FormulusMessageHandlers.ts b/formulus/src/webview/FormulusMessageHandlers.ts index fd54b9471..b56958f15 100644 --- a/formulus/src/webview/FormulusMessageHandlers.ts +++ b/formulus/src/webview/FormulusMessageHandlers.ts @@ -396,8 +396,8 @@ export function createFormulusMessageHandlers(): FormulusMessageHandlers { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( /[xy]/g, function (c) { - const r = (Math.random() * 16) | 0; - const v = c == 'x' ? r : (r & 0x3) | 0x8; + const r = Math.floor(Math.random() * 16); + const v = c === 'x' ? r : (r % 4) + 8; return v.toString(16); }, ); @@ -416,8 +416,6 @@ export function createFormulusMessageHandlers(): FormulusMessageHandlers { }, ); - // Use RNFS to copy the camera image to both attachment locations - const RNFS = require('react-native-fs'); const attachmentsDirectory = `${RNFS.DocumentDirectoryPath}/attachments`; const pendingUploadDirectory = `${RNFS.DocumentDirectoryPath}/attachments/pending_upload`; @@ -580,8 +578,6 @@ export function createFormulusMessageHandlers(): FormulusMessageHandlers { result.data && result.data.base64 ) { - const RNFS = require('react-native-fs'); - // Generate a unique filename const timestamp = Date.now(); const filename = `signature_${timestamp}.png`; @@ -900,7 +896,6 @@ export function createFormulusMessageHandlers(): FormulusMessageHandlers { console.log('Audio recording completed:', result); // Get file stats for metadata - const RNFS = require('react-native-fs'); const fileStats = await RNFS.stat(audioPath); // Create AudioResult object matching our interface