diff --git a/android/app/build.gradle b/android/app/build.gradle index f7fa3a9..1af43bb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -311,3 +311,9 @@ def isNewArchitectureEnabled() { // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true` return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" } + +project.ext.vectoricons = [ + iconFontNames: [ 'MaterialIcons.ttf', 'MaterialCommunityIcons.ttf', 'FontAwesome.ttf', 'Ionicons.ttf' ] // Name of the font files you want to copy +] + +apply from: "../../node_modules/react-native-vector-icons/fonts.gradle" \ No newline at end of file diff --git a/index.web.tsx b/index.web.tsx index 440b36a..9ad4e24 100644 --- a/index.web.tsx +++ b/index.web.tsx @@ -3,13 +3,87 @@ import {AppRegistry} from 'react-native'; import App from './src/App'; -//AppRegistry.registerComponent("App", () => App) +// @ts-ignore +import outfitFont from './web/public/fonts/Outfit.ttf'; +// @ts-ignore +import FontAwesome from 'react-native-vector-icons/Fonts/FontAwesome.ttf'; +// @ts-ignore +import MaterialIcons from 'react-native-vector-icons/Fonts/MaterialIcons.ttf'; +// @ts-ignore +import Ionicons from 'react-native-vector-icons/Fonts/Ionicons.ttf'; +// @ts-ignore +import MaterialCommunityIcons from 'react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf'; -/* -AppRegistry.runApplication("App", { - initialProps: {}, - rootTag: document.getElementById("root"), -})*/ +const iconFontStyles = `@font-face { + src: url(${MaterialCommunityIcons}); + font-family: MaterialCommunityIcons; +} +@font-face { + src: url(${FontAwesome}); + font-family: FontAwesome; +} +@font-face { + src: url(${MaterialIcons}); + font-family: MaterialIcons; +} +@font-face { + src: url(${Ionicons}); + font-family: Ionicons; +} +@font-face { + font-family: 'Outfit-Thin'; + src: url(${outfitFont}); + font-weight: 100; +} +@font-face { + font-family: 'Outfit-ExtraLight'; + src: url(${outfitFont}); + font-weight: 200; +} +@font-face { + font-family: 'Outfit-Light'; + src: url(${outfitFont}); + font-weight: 300; +} +@font-face { + font-family: 'Outfit-Regular'; + src: url(${outfitFont}); + font-weight: 400; +} +@font-face { + font-family: 'Outfit-Medium'; + src: url(${outfitFont}); + font-weight: 500; +} +@font-face { + font-family: 'Outfit-SemiBold'; + src: url(${outfitFont}); + font-weight: 600; +} +@font-face { + font-family: 'Outfit-Bold'; + src: url(${outfitFont}); + font-weight: 700; +} +@font-face { + font-family: 'Outfit-ExtraBold'; + src: url(${outfitFont}); + font-weight: 800; +} +@font-face { + font-family: 'Outfit-Black'; + src: url(${outfitFont}); + font-weight: 900; +} + +`; + +const style = document.createElement('style'); +style.type = 'text/css'; + +style.appendChild(document.createTextNode(iconFontStyles)); + +document.head.appendChild(style); const root = createRoot(document.getElementById('root') as HTMLElement); root.render(); diff --git a/package-lock.json b/package-lock.json index 53aed4f..931fc8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,14 @@ "name": "ClickAndJoinApp", "version": "0.0.1", "dependencies": { + "@react-native-masked-view/masked-view": "^0.2.8", "@react-navigation/bottom-tabs": "^6.5.2", "@react-navigation/native": "^6.1.1", "@react-navigation/native-stack": "^6.9.7", "@react-spring/native": "^9.6.1", "@react-spring/web": "^9.6.1", "@reduxjs/toolkit": "^1.9.1", + "@types/react-native-vector-icons": "^6.4.12", "babel-preset-es2015": "^6.24.1", "babel-preset-esnext": "^1.1.3", "babel-preset-react": "^6.24.1", @@ -26,8 +28,12 @@ "react-native-reanimated": "^2.13.0", "react-native-safe-area-context": "^4.4.1", "react-native-screens": "^3.18.2", + "react-native-svg": "^13.6.0", + "react-native-user-agent": "^2.3.1", + "react-native-vector-icons": "^9.2.0", "react-native-web": "^0.18.10", - "react-redux": "^8.0.5" + "react-redux": "^8.0.5", + "react-string-replace": "^1.1.0" }, "devDependencies": { "@babel/core": "^7.12.9", @@ -4964,6 +4970,15 @@ "integrity": "sha512-+zDZ20NUnSWghj7Ku5aFphMzuM9JulqCW+aPXT6IfIXFbb8tzYTTOSeRFOtuekJ99ibW2fUCSsjuKNlwDIbHFg==", "dev": true }, + "node_modules/@react-native-masked-view/masked-view": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@react-native-masked-view/masked-view/-/masked-view-0.2.8.tgz", + "integrity": "sha512-+1holBPDF1yi/y0uc1WB6lA5tSNHhM7PpTMapT3ypvSnKQ9+C6sy/zfjxNxRA/llBQ1Ci6f94EaK56UCKs5lTA==", + "peerDependencies": { + "react": ">=16", + "react-native": ">=0.57" + } + }, "node_modules/@react-native/assets": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz", @@ -5906,11 +5921,19 @@ "version": "0.70.7", "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.7.tgz", "integrity": "sha512-hBzeUWwk8sfj3vDfwEXb4hbjWjl0jb5CvWlu2gLrOUJyFHVzJ+x6Y9ilO2eVtJW7l5QmmNLILE1PkVfKRkqYuQ==", - "peer": true, "dependencies": { "@types/react": "*" } }, + "node_modules/@types/react-native-vector-icons": { + "version": "6.4.12", + "resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.12.tgz", + "integrity": "sha512-gSXtv3NMOsRwSXJ/gvGebm7CNjHbkeFKCse9h/Pvi+x2yjCLOkJR1FBfec06DhaFJpsK7Y8WRQpwOS0eLqx5Rg==", + "dependencies": { + "@types/react": "*", + "@types/react-native": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -8154,8 +8177,7 @@ "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "peer": true + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -8955,7 +8977,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "peer": true, "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -8971,7 +8992,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "peer": true, "dependencies": { "mdn-data": "2.0.14", "source-map": "^0.6.1" @@ -8984,7 +9004,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "peer": true, "engines": { "node": ">= 6" }, @@ -9304,7 +9323,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "peer": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -9329,8 +9347,7 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ], - "peer": true + ] }, "node_modules/domexception": { "version": "2.0.1", @@ -9357,7 +9374,6 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "peer": true, "dependencies": { "domelementtype": "^2.3.0" }, @@ -9372,7 +9388,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", - "peer": true, "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -9463,7 +9478,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", - "peer": true, "engines": { "node": ">=0.12" }, @@ -14123,9 +14137,9 @@ "dev": true }, "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", + "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==", "bin": { "json5": "lib/cli.js" }, @@ -14481,8 +14495,7 @@ "node_modules/mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "peer": true + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" }, "node_modules/media-typer": { "version": "0.3.0", @@ -15730,7 +15743,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "peer": true, "dependencies": { "boolbase": "^1.0.0" }, @@ -16849,7 +16861,6 @@ "version": "13.6.0", "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.6.0.tgz", "integrity": "sha512-1wjHCMJ8siyZbDZ0MX5wM+Jr7YOkb6GADn4/Z+/u1UwJX8WfjarypxDF3UO1ugMHa+7qor39oY+URMcrgPpiww==", - "peer": true, "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3" @@ -16859,6 +16870,116 @@ "react-native": "*" } }, + "node_modules/react-native-user-agent": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/react-native-user-agent/-/react-native-user-agent-2.3.1.tgz", + "integrity": "sha512-AIFr1VgJHwgWmMwCOmIGxuBeAaADlouXKc10UyR4fzWneUbt5uIJIoRu2oExlfCtiT8IyCp106khDD5vx7RUjw==", + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/react-native-vector-icons": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-9.2.0.tgz", + "integrity": "sha512-wKYLaFuQST/chH3AJRjmOLoLy3JEs1JR6zMNgTaemFpNoXs0ztRnTxcxFD9xhX7cJe1/zoN5BpQYe7kL0m5yyA==", + "dependencies": { + "prop-types": "^15.7.2", + "yargs": "^16.1.1" + }, + "bin": { + "fa5-upgrade": "bin/fa5-upgrade.sh", + "generate-icon": "bin/generate-icon.js" + } + }, + "node_modules/react-native-vector-icons/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/react-native-vector-icons/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/react-native-vector-icons/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/react-native-vector-icons/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/react-native-vector-icons/node_modules/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==", + "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/react-native-vector-icons/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native-vector-icons/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, "node_modules/react-native-web": { "version": "0.18.10", "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.18.10.tgz", @@ -16958,6 +17079,14 @@ "react": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-string-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-string-replace/-/react-string-replace-1.1.0.tgz", + "integrity": "sha512-N6RalSDFGbOHs0IJi1H611WbZsvk3ZT47Jl2JEXFbiS3kTwsdCYij70Keo/tWtLy7sfhDsYm7CwNM/WmjXIaMw==", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/react-test-renderer": { "version": "18.1.0", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.1.0.tgz", @@ -24109,6 +24238,12 @@ "integrity": "sha512-+zDZ20NUnSWghj7Ku5aFphMzuM9JulqCW+aPXT6IfIXFbb8tzYTTOSeRFOtuekJ99ibW2fUCSsjuKNlwDIbHFg==", "dev": true }, + "@react-native-masked-view/masked-view": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@react-native-masked-view/masked-view/-/masked-view-0.2.8.tgz", + "integrity": "sha512-+1holBPDF1yi/y0uc1WB6lA5tSNHhM7PpTMapT3ypvSnKQ9+C6sy/zfjxNxRA/llBQ1Ci6f94EaK56UCKs5lTA==", + "requires": {} + }, "@react-native/assets": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz", @@ -24892,11 +25027,19 @@ "version": "0.70.7", "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.7.tgz", "integrity": "sha512-hBzeUWwk8sfj3vDfwEXb4hbjWjl0jb5CvWlu2gLrOUJyFHVzJ+x6Y9ilO2eVtJW7l5QmmNLILE1PkVfKRkqYuQ==", - "peer": true, "requires": { "@types/react": "*" } }, + "@types/react-native-vector-icons": { + "version": "6.4.12", + "resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.12.tgz", + "integrity": "sha512-gSXtv3NMOsRwSXJ/gvGebm7CNjHbkeFKCse9h/Pvi+x2yjCLOkJR1FBfec06DhaFJpsK7Y8WRQpwOS0eLqx5Rg==", + "requires": { + "@types/react": "*", + "@types/react-native": "*" + } + }, "@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -26820,8 +26963,7 @@ "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "peer": true + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "brace-expansion": { "version": "1.1.11", @@ -27432,7 +27574,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "peer": true, "requires": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -27445,7 +27586,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "peer": true, "requires": { "mdn-data": "2.0.14", "source-map": "^0.6.1" @@ -27454,8 +27594,7 @@ "css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "peer": true + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" }, "cssom": { "version": "0.4.4", @@ -27692,7 +27831,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "peer": true, "requires": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -27708,8 +27846,7 @@ "domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "peer": true + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" }, "domexception": { "version": "2.0.1", @@ -27732,7 +27869,6 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "peer": true, "requires": { "domelementtype": "^2.3.0" } @@ -27741,7 +27877,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", - "peer": true, "requires": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -27810,8 +27945,7 @@ "entities": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", - "peer": true + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==" }, "envinfo": { "version": "7.8.1", @@ -31309,9 +31443,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", + "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==" }, "jsonfile": { "version": "4.0.0", @@ -31591,8 +31725,7 @@ "mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "peer": true + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" }, "media-typer": { "version": "0.3.0", @@ -32625,7 +32758,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "peer": true, "requires": { "boolbase": "^1.0.0" } @@ -33473,12 +33605,93 @@ "version": "13.6.0", "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.6.0.tgz", "integrity": "sha512-1wjHCMJ8siyZbDZ0MX5wM+Jr7YOkb6GADn4/Z+/u1UwJX8WfjarypxDF3UO1ugMHa+7qor39oY+URMcrgPpiww==", - "peer": true, "requires": { "css-select": "^5.1.0", "css-tree": "^1.1.3" } }, + "react-native-user-agent": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/react-native-user-agent/-/react-native-user-agent-2.3.1.tgz", + "integrity": "sha512-AIFr1VgJHwgWmMwCOmIGxuBeAaADlouXKc10UyR4fzWneUbt5uIJIoRu2oExlfCtiT8IyCp106khDD5vx7RUjw==", + "requires": {} + }, + "react-native-vector-icons": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-9.2.0.tgz", + "integrity": "sha512-wKYLaFuQST/chH3AJRjmOLoLy3JEs1JR6zMNgTaemFpNoXs0ztRnTxcxFD9xhX7cJe1/zoN5BpQYe7kL0m5yyA==", + "requires": { + "prop-types": "^15.7.2", + "yargs": "^16.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "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==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + } + } + }, "react-native-web": { "version": "0.18.10", "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.18.10.tgz", @@ -33537,6 +33750,11 @@ "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" } }, + "react-string-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-string-replace/-/react-string-replace-1.1.0.tgz", + "integrity": "sha512-N6RalSDFGbOHs0IJi1H611WbZsvk3ZT47Jl2JEXFbiS3kTwsdCYij70Keo/tWtLy7sfhDsYm7CwNM/WmjXIaMw==" + }, "react-test-renderer": { "version": "18.1.0", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.1.0.tgz", diff --git a/package.json b/package.json index b68f8c1..0bc6348 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,14 @@ "lint": "eslint ." }, "dependencies": { + "@react-native-masked-view/masked-view": "^0.2.8", "@react-navigation/bottom-tabs": "^6.5.2", "@react-navigation/native": "^6.1.1", "@react-navigation/native-stack": "^6.9.7", "@react-spring/native": "^9.6.1", "@react-spring/web": "^9.6.1", "@reduxjs/toolkit": "^1.9.1", + "@types/react-native-vector-icons": "^6.4.12", "babel-preset-es2015": "^6.24.1", "babel-preset-esnext": "^1.1.3", "babel-preset-react": "^6.24.1", @@ -30,8 +32,12 @@ "react-native-reanimated": "^2.13.0", "react-native-safe-area-context": "^4.4.1", "react-native-screens": "^3.18.2", + "react-native-svg": "^13.6.0", + "react-native-user-agent": "^2.3.1", + "react-native-vector-icons": "^9.2.0", "react-native-web": "^0.18.10", - "react-redux": "^8.0.5" + "react-redux": "^8.0.5", + "react-string-replace": "^1.1.0" }, "devDependencies": { "@babel/core": "^7.12.9", @@ -59,7 +65,7 @@ "webpack-dev-server": "^4.11.1" }, "jest": { - "preset": "react-native", + "preset": "react-native-web", "transform": { "^.+\\.(js)$": "/node_modules/babel-jest", "\\.(ts|tsx)$": "ts-jest" diff --git a/src/App.tsx b/src/App.tsx index a2a366e..a0ad896 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import {NavigationContainer} from '@react-navigation/native'; import {getBackgroundColor, theme, ThemeSwitcher} from '@caj/configs/colors'; import StatusBar from './StatusBar'; -import Navigation, {linking} from './Navigation'; +import Navigation, {linking} from './caj/Navigation'; import {Provider, useSelector} from 'react-redux'; import {RootState, store} from '@caj/redux/store'; @@ -44,8 +44,8 @@ const OtherProviders = () => { }; return ( - - + + diff --git a/src/appStart/StartHelper.tsx b/src/appStart/StartHelper.tsx index acb37fa..c3d184e 100644 --- a/src/appStart/StartHelper.tsx +++ b/src/appStart/StartHelper.tsx @@ -23,7 +23,7 @@ function onAppStart() { console.log('finish'); setTimeout(() => { store.dispatch(appNonSaveVarActions.setAppStatus(appStatus.APP_RUNNING)); - }, 500); + }, 250); //store.dispatch(actions.loadPreferences(appVar)); }); } diff --git a/src/Navigation.tsx b/src/caj/Navigation copy.tsx similarity index 85% rename from src/Navigation.tsx rename to src/caj/Navigation copy.tsx index 8e68491..1af1c1a 100644 --- a/src/Navigation.tsx +++ b/src/caj/Navigation copy.tsx @@ -34,25 +34,24 @@ const styles = StyleSheet.create({ }); export const linking: LinkingOptions<{ - Auth: string; + Maps: string; Home: string; - Table: string; - Loading: string; + Chat: string; + Settings: string; }> = { prefixes: ['http://'], config: { - initialRouteName: 'Loading', screens: { - Auth: 'auth', - Home: 'home', - Table: 'table', - Loading: 'loading', + Maps: 'mapss', + Home: 'account', + Chat: 'chats', + Settings: 'settingss', }, }, }; export type HomeStackNavigatorParamList = { - Home: undefined; + Account: undefined; Maps: undefined; Chat: undefined; Test: undefined; @@ -100,12 +99,21 @@ function HomeScreen() { ); } +export type SettingsStackNavigatorParamList = { + Chat: undefined; + Settings: undefined; +}; + +export type SettingsScreenNavigationProp = + NativeStackNavigationProp; + function SettingsScreen() { - const navigation = useNavigation(); + const navigation = useNavigation(); + const navigation2 = useNavigation(); return ( Settings screen - + ); } diff --git a/src/caj/Navigation.tsx b/src/caj/Navigation.tsx new file mode 100644 index 0000000..44b23ec --- /dev/null +++ b/src/caj/Navigation.tsx @@ -0,0 +1,126 @@ +import React, {useState, useEffect} from 'react'; + +import {StyleSheet, Appearance, Dimensions} from 'react-native'; +import {SafeAreaProvider, SafeAreaView} from 'react-native-safe-area-context'; + +import {useSelector, useDispatch} from 'react-redux'; +import {RootState} from '@caj/redux/store'; +import {appVarActions} from '@caj/configs/appVarReducer'; + +import imgSrc from '@caj/img/maimg.png'; +import {placeholder} from '@caj/lang/default'; +import {getBackgroundColor} from '@caj/configs/colors'; +import {saveVarChanges} from '@caj/helper/appData'; + +import {Box, Input, VStack, Center, Avatar, Text, Button} from 'native-base'; +import {View} from 'react-native'; + +import { + LinkingOptions, + NavigationContainer, + NavigatorScreenParams, + useNavigation, +} from '@react-navigation/native'; +import { + createNativeStackNavigator, + NativeStackNavigationProp, +} from '@react-navigation/native-stack'; +import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; +import AccountTab, { + AccountStackNavigatorParamList, + AccountTabName, +} from './tabs/main/AccountTab'; +import CalendarTab, { + CalendarStackNavigatorParamList, +} from './tabs/main/CalendarTab'; +import MapsTab, {MapsStackNavigatorParamList} from './tabs/main/MapsTab'; +import ChatTab, {ChatStackNavigatorParamList} from './tabs/main/ChatTab'; +import { + FadeInView, + SlideFromLeftView, + SlideFromRightView, +} from './helper/animations'; +import { + LoginStackNavigatorParamList, + RegisterScreenAnim, +} from './components/NotLoggedIn'; + +const styles = StyleSheet.create({ + container: { + height: '100%', + flex: 1, + }, +}); + +export const linking: LinkingOptions<{}> = { + prefixes: ['http://'], +}; + +export type RootStackNavigatorParamList = { + Home: NavigatorScreenParams; + Register: NavigatorScreenParams; +}; + +export type RootScreenNavigationProp = + NativeStackNavigationProp; + +export default function Navigation() { + return ( + + + + + ); +} + +const Stack = createNativeStackNavigator(); + +export type HomeStackNavigatorParamList = { + Account: NavigatorScreenParams; + Calendar: NavigatorScreenParams; + Maps: NavigatorScreenParams; + Chat: NavigatorScreenParams; +}; + +function AccountTabAnim(props: any) { + return ( + + + + ); +} + +function CalendarTabAnim(props: any) { + return ( + + + + ); +} +function MapsTabAnim(props: any) { + return ( + + + + ); +} +function ChatTabAnim(props: any) { + return ( + + + + ); +} + +function HomeStack() { + return ( + + + + + + + ); +} + +const Tab = createBottomTabNavigator(); diff --git a/src/caj/components/AccountInfoBanner.tsx b/src/caj/components/AccountInfoBanner.tsx new file mode 100644 index 0000000..0f3532d --- /dev/null +++ b/src/caj/components/AccountInfoBanner.tsx @@ -0,0 +1,5 @@ +import {Center} from 'native-base'; + +export default function AccountInfoBanner() { + return
Account
; +} diff --git a/src/caj/components/ConfirmationCodeField.tsx b/src/caj/components/ConfirmationCodeField.tsx new file mode 100644 index 0000000..cf7d2aa --- /dev/null +++ b/src/caj/components/ConfirmationCodeField.tsx @@ -0,0 +1,146 @@ +import {Box, Center, HStack, Input, Pressable, Text} from 'native-base'; +import {useEffect, useRef, useState} from 'react'; +import { + TextInput, + NativeSyntheticEvent, + TextInputChangeEventData, +} from 'react-native'; + +function RenderCell(props: { + index: number; + text: string | number; + maxIndex: number; + isFocus: boolean; + disabled: boolean; + rest: any; +}) { + const display: string = props.text ? props.text.toString() : ''; + + const [toggle, setToggle] = useState(true); + + useEffect(() => { + if (props.isFocus === false) return; + + let timer = setInterval(() => { + setToggle(!toggle); + }, 500); + return () => { + clearInterval(timer); + }; + }); + + return ( + +
+ + {props.isFocus ? (toggle ? '|' : '') : display} + +
+
+ ); +} + +function ConfirmationCodeField(props: { + cellCount: number; + charType: 'number' | 'letter'; + onFinish?: Function; + onChange?: Function; + disabled?: boolean; + rest?: any; +}) { + const [values, setValues] = useState({input: ''}); + const [isFocus, setFocus] = useState(false); + + const inputRef = useRef(null); + + return ( + + setFocus(true)} + onBlur={() => setFocus(false)} + editable={!props.disabled} + onChange={(event: NativeSyntheticEvent) => { + let text = event.nativeEvent.text; + let vals = {...values}; + + if (props.charType === 'number') + vals.input = text.replace(/[^0-9]/g, ''); + else if (props.charType === 'letter') vals.input = text; + + vals.input = vals.input.slice(0, props.cellCount); + + setValues(vals); + + if (props.onChange) props.onChange(vals.input); + + if (vals.input.length === props.cellCount) { + if (inputRef.current !== null) { + inputRef.current.blur(); + if (props.onFinish) props.onFinish(vals.input); + } + } + }} + style={{ + position: 'absolute', + opacity: 0, + width: 1, + height: 1, + + padding: 0, + margin: 0, + }} + /> + + {(() => { + let cells = []; + for (let i = 0; i < props.cellCount; i++) { + cells.push( + { + let vals = {...values}; + vals.input = vals.input.slice(0, i); + setValues(vals); + + if (props.onChange) props.onChange(vals.input); + + if (inputRef.current !== null) { + inputRef.current.focus(); + } + } + : undefined, + }} + />, + ); + } + return cells; + })()} + + + ); +} + +export default ConfirmationCodeField; diff --git a/src/caj/components/NameDisplay.tsx b/src/caj/components/NameDisplay.tsx new file mode 100644 index 0000000..fd0bdcb --- /dev/null +++ b/src/caj/components/NameDisplay.tsx @@ -0,0 +1,30 @@ +import {AccountName, Username} from '@caj/helper/types'; +import {RootState} from '@caj/redux/store'; +import {HStack, Text} from 'native-base'; +import {useSelector} from 'react-redux'; + +export default function NameDisplay(props: { + UserName: Username; + AccountName: AccountName; + fontSize?: number; +}) { + const theme = useSelector( + (state: RootState) => state.appVariables.preferences.theme, + ); + const fontSize = props.fontSize || 15; + const lineHeight = fontSize * 1.25; + + return ( + + + {props.UserName !== '' ? props.UserName : '----'} + + + {props.AccountName !== '' ? props.AccountName : '----'} + + + ); +} diff --git a/src/caj/components/NotLoggedIn.tsx b/src/caj/components/NotLoggedIn.tsx new file mode 100644 index 0000000..1db50f1 --- /dev/null +++ b/src/caj/components/NotLoggedIn.tsx @@ -0,0 +1,960 @@ +import {RegisterProcess, ThemeMode} from '@caj/configs/appVar'; +import {appVarActions} from '@caj/configs/appVarReducer'; +import {defaultHeaderStyle} from '@caj/configs/colors'; +import {SlideFromLeftView} from '@caj/helper/animations'; +import {saveVarChanges} from '@caj/helper/appData'; +import {apiBackendRequest, makeRequest} from '@caj/helper/request'; +import { + accountNameOptions, + EMail, + emailOptions, + passwordOptions, + userNameOptions, + XToken, +} from '@caj/helper/types'; +import {RootScreenNavigationProp} from '@caj/Navigation'; +import {RootState, store} from '@caj/redux/store'; +import {useNavigation} from '@react-navigation/native'; +import { + createNativeStackNavigator, + NativeStackNavigationProp, +} from '@react-navigation/native-stack'; +import { + Box, + Button, + Center, + Container, + FormControl, + Heading, + HStack, + Icon, + IconButton, + Input, + Pressable, + ScrollView, + Spinner, + Text, + useColorModeValue, + useTheme, + useToast, + VStack, + WarningOutlineIcon, +} from 'native-base'; + +import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; + +import {baseFontSize} from 'native-base/lib/typescript/theme/tools'; +import {useEffect, useRef, useState} from 'react'; +import {useDispatch, useSelector} from 'react-redux'; +import reactStringReplace from 'react-string-replace'; +import ConfirmationCodeField from './ConfirmationCodeField'; +import NameDisplay from './NameDisplay'; +import showToast from './Toast'; +import {NativeSyntheticEvent, TextInputFocusEventData} from 'react-native'; + +const validateEmail = (email: EMail) => { + return emailOptions.isAllowed(email); +}; + +export default function NotLoggedIn() { + const lang = useSelector((state: RootState) => state.appVariables.lang); + const theme = useSelector( + (state: RootState) => state.appVariables.preferences.theme, + ); + + const toast = useToast(); + + const navigation = useNavigation(); + + return ( + + {lang.appName} + {lang.appNameDesc} + + + + + + ); +} + +export function RegisterScreenAnim(props: any) { + return ( + + + + ); +} + +export type LoginStackNavigatorParamList = { + RegStepOne: undefined; + RegStepTwo: undefined; + RegStepFinal: undefined; +}; + +const LoginStack = createNativeStackNavigator(); + +export type LoginScreenNavigationProp = + NativeStackNavigationProp; + +function RegisterScreen() { + const lang = useSelector((state: RootState) => state.appVariables.lang); + const theme = useSelector( + (state: RootState) => state.appVariables.preferences.theme, + ); + + return ( + + + + + + ); +} + +function Agreement() { + const toast = useToast(); + const lang = useSelector((state: RootState) => state.appVariables.lang); + + const textColor = useColorModeValue('blue.700', 'cyan.400'); + + let replacedText = reactStringReplace( + lang.account.registration.info, + '${TermsOfUse}', + (match, i) => ( + { + showToast(toast, { + title: lang.account.registration.termsOfUse, + variant: 'solid', + status: 'info', + description: undefined, + isClosable: true, + rest: {colorScheme: 'primary'}, + }); + }}> + {lang.account.registration.termsOfUse} + + ), + ); + + replacedText = reactStringReplace( + replacedText, + '${privacyPolicy}', + (match, i) => ( + { + showToast(toast, { + title: lang.account.registration.privacyPolicy, + variant: 'solid', + status: 'info', + description: undefined, + isClosable: true, + rest: {colorScheme: 'primary'}, + }); + }}> + {lang.account.registration.privacyPolicy} + + ), + ); + + return ( + + {replacedText} + + ); +} + +function resendMail(email: EMail, toast: any): Promise { + return new Promise((resolve, reject) => { + makeRequest({ + path: apiBackendRequest.REGISTER_RESEND_MAIL, + + requestHeader: {}, + request: { + Email: email, + }, + //response: { + // XToken: undefined, + //}, + }) + .then(resp => { + console.log(1); + let token = + store.getState().appVariables.preferences.RegisterProcess.XToken; + if (token !== undefined /*resp.response.XToken !== undefined*/) { + showToast(toast, { + title: store.getState().appVariables.lang.info, + variant: 'solid', + status: 'info', + description: + store.getState().appVariables.lang.account.registration.stepTwo + .resend[2], + isClosable: true, + rest: {}, + }); + + resolve(token); + } else { + reject(500); + showToast(toast, { + title: store.getState().appVariables.lang.error, + variant: 'solid', + status: 'error', + description: 'XToken is undefined', + isClosable: true, + rest: {}, + }); + } + }) + .catch(resp => { + let text = 'unknown error ' + resp.status; + if (resp.status !== undefined) { + const _text = + store.getState().appVariables.lang.account.registration.stepTwo + .resendError[resp.status as number]; + if (_text !== undefined) text = _text; + } + + showToast(toast, { + title: store.getState().appVariables.lang.error, + variant: 'solid', + status: 'error', + description: text, + isClosable: true, + rest: {}, + }); + + reject(resp.status); + }); + }); +} + +function StepOne() { + const lang = useSelector((state: RootState) => state.appVariables.lang); + const regPro = useSelector( + (state: RootState) => state.appVariables.preferences.RegisterProcess, + ); + + const dispatch = useDispatch(); + const toast = useToast(); + + const navigation = useNavigation(); + + const initNoErrors = { + wrongFormat: false, + alreadyExists: false, + noEntered: false, + unknown: undefined, + }; + + const [errors, setErrors] = useState(initNoErrors); + + const isError = + errors.wrongFormat || + errors.alreadyExists || + errors.noEntered || + errors.unknown !== undefined; + + const errorText = () => { + if (errors.wrongFormat) { + return lang.account.registration.stepOne.addressInvalid; + } + if (errors.alreadyExists) { + return lang.account.registration.stepOne.addressExists; + } + if (errors.noEntered) { + return lang.account.registration.stepOne.noMailEntered; + } + if (errors.unknown !== undefined) { + return errors.unknown; + } + }; + + const [isLoading, setLoading] = useState(false); + + const [values, setValues] = useState({email: regPro.EMail}); + + useEffect(() => { + if (regPro.isRegistering === 'stepTwo') { + setLoading(true); + setErrors(initNoErrors); + + setTimeout(nextStep, 500); + } else if (regPro.isRegistering === 'stepFinal') { + setLoading(true); + setErrors(initNoErrors); + + setTimeout(nextStep, 500); + } + }, []); + + const nextStep = () => { + setLoading(true); + setErrors(initNoErrors); + + makeRequest({ + path: apiBackendRequest.REGISTER_STEP_1, + requestHeader: {}, + request: { + Email: values.email, + }, + response: { + XToken: undefined, + }, + }) + .then(resp => { + let rp = {...regPro}; + rp.isRegistering = 'stepTwo'; + rp.EMail = values.email; + rp.XToken = resp.response.XToken; + + dispatch(appVarActions.setRegisterProcess(rp)); + saveVarChanges(); + + showToast(toast, { + title: lang.account.registration.stepOne.success, + variant: 'solid', + status: 'success', + description: undefined, + isClosable: true, + rest: {colorScheme: 'primary'}, + }); + + navigation.navigate('Register', {screen: 'RegStepTwo'}); + setLoading(false); + }) + .catch(resp => { + if (resp.status === 401 || resp.status === 204) { + if (regPro.XToken !== undefined) { + let rp = {...regPro}; + + if (resp.status === 401) { + rp.isRegistering = 'stepTwo'; + rp.EMail = values.email; + + dispatch(appVarActions.setRegisterProcess(rp)); + saveVarChanges(); + + navigation.navigate('Register', {screen: 'RegStepTwo'}); + } else if (resp.status === 204) { + rp.isRegistering = 'stepFinal'; + rp.EMail = values.email; + + dispatch(appVarActions.setRegisterProcess(rp)); + saveVarChanges(); + + navigation.navigate('Register', {screen: 'RegStepFinal'}); + } + + setLoading(false); + } else { + resendMail(values.email, toast) + .then(() => { + setLoading(false); + }) + .catch(() => { + setLoading(false); + }); + } + } else { + showToast(toast, { + title: 'Error', + variant: 'solid', + status: 'error', + description: resp.status, + isClosable: true, + rest: {colorScheme: 'primary'}, + }); + setLoading(false); + } + }); + }; + + return ( + + + + + {lang.account.registration.stepOne.title} + + { + const mail = text.replaceAll(' ', ''); + setValues({email: mail}); + + if (errors.noEntered && mail !== '') { + let err = errors; + err.noEntered = false; + setErrors({...err}); + } + if (errors.wrongFormat && validateEmail(mail)) { + let err = errors; + err.wrongFormat = false; + setErrors({...err}); + } + if (errors.alreadyExists) { + let err = errors; + err.alreadyExists = false; + setErrors({...err}); + } + }} + /> + + }> + {errorText()} + + + + + + + + + ); +} + +function StepTwo() { + const lang = useSelector((state: RootState) => state.appVariables.lang); + const regPro = useSelector( + (state: RootState) => state.appVariables.preferences.RegisterProcess, + ); + + const dispatch = useDispatch(); + + const cellCount = 6; + const {colors} = useTheme(); + + const toast = useToast(); + + const navigation = useNavigation(); + + const initNoErrors = { + noEntered: false, + }; + + const [errors, setErrors] = useState(initNoErrors); + + const [isLoading, setLoading] = useState(false); + + const [values, setValues] = useState({code: ''}); + + const headerText = () => { + return reactStringReplace( + lang.account.registration.stepTwo.title, + '${EMail}', + (match, i) => ( + + {regPro.EMail} + + ), + ); + }; + + const resendText = () => { + return reactStringReplace( + lang.account.registration.stepTwo.resend[0], + '${resend}', + (match, i) => ( + { + setLoading(true); + resendMail( + store.getState().appVariables.preferences.RegisterProcess.EMail, + toast, + ) + .then(() => { + setLoading(false); + }) + .catch(() => { + setLoading(false); + }); + }}> + {lang.account.registration.stepTwo.resend[1]} + + ), + ); + }; + + const validate = (text: string) => { + setLoading(true); + setTimeout(() => { + makeRequest({ + path: apiBackendRequest.REGISTER_STEP_2, + + requestHeader: {}, + requestGET: {':verifyId': text, ':xToken': regPro.XToken || ''}, + }) + .then(resp => { + let rp = {...regPro}; + rp.isRegistering = 'stepFinal'; + + dispatch(appVarActions.setRegisterProcess(rp)); + saveVarChanges(); + + showToast(toast, { + title: lang.account.registration.stepTwo.success, + variant: 'solid', + status: 'success', + description: undefined, + isClosable: true, + rest: {colorScheme: 'primary'}, + }); + + navigation.navigate('Register', {screen: 'RegStepFinal'}); + setLoading(false); + }) + .catch(resp => { + let text = 'unknown error ' + resp.status; + if (resp.status !== undefined) { + const _text = + lang.account.registration.stepTwo.verificationError[ + resp.status as number + ]; + if (_text !== undefined) text = _text; + } + + showToast(toast, { + title: lang.error, + variant: 'solid', + status: 'error', + description: text, + isClosable: true, + rest: {}, + }); + + setLoading(false); + if (resp.status === 422) { + let rp = {...regPro}; + rp.isRegistering = false; + + dispatch(appVarActions.setRegisterProcess(rp)); + saveVarChanges(); + navigation.navigate('Register', {screen: 'RegStepOne'}); + } + }); + }, 500); + }; + + return ( + + +
+ {headerText()} + + + { + setValues({code: text}); + setErrors(initNoErrors); + }} + onFinish={(text: string) => validate(text)} + /> + + }> + {lang.account.registration.stepTwo.noCodeEntered} + + + +
+ + + + + + + {resendText()} + + +
+
+ ); +} +function StepFinal() { + const lang = useSelector((state: RootState) => state.appVariables.lang); + const regPro = useSelector( + (state: RootState) => state.appVariables.preferences.RegisterProcess, + ); + + const dispatch = useDispatch(); + + const {colors} = useTheme(); + + const toast = useToast(); + + const navigation = useNavigation(); + + const [isLoading, setLoading] = useState(false); + const [showPassword, setShowPassword] = useState(false); + + interface inputElementType { + label: string; + input: string; + autoCapitalize: 'none' | 'words'; + errorIndex: 'none' | string; + errorTextObject: any; + isPassword: boolean | 'passwordRepeat'; + minLength: number; + maxLength: number; + onTextChange: any; + textChangeTimeout: number; + isAllowed: any; + } + + const accountNameRef = useRef(setTimeout(() => {})); + const accountNameFetchRef = useRef(setTimeout(() => {})); + + const accountName = { + label: lang.account.registration.stepFinal.accountName, + input: '', + errorIndex: 'none', + errorTextObject: lang.account.registration.stepFinal.accountNameError, + minLength: accountNameOptions.minLength, + maxLength: accountNameOptions.maxLength, + isAllowed: accountNameOptions.isAllowed, + isPassword: false, + onTextChange: (e: NativeSyntheticEvent) => { + let text = e.nativeEvent.text; + let self = accountName; + + clearTimeout(accountNameRef.current); + + let obj = {...valuesAccountName}; + accountNameRef.current = setTimeout(() => { + obj.input = text; + + if (text.length < self.minLength) obj.errorIndex = 'tooShort'; + else if (text.length > self.maxLength) obj.errorIndex = 'tooLong'; + else if (self.isAllowed(text) === false) obj.errorIndex = 'invalid'; + else obj.errorIndex = 'none'; + + setValuesAccountName(obj); + }, 50); + + clearTimeout(accountNameFetchRef.current); + accountNameFetchRef.current = setTimeout(() => { + console.log(obj); + + if (obj.errorIndex === 'none') { + makeRequest({ + path: apiBackendRequest.REGISTER_STEP_FINAL_ACCOUNT_NAME_CHECK, + request: {AccountName: obj.input}, + }) + .then(resp => { + console.log('OK'); + }) + .catch(resp => { + if (resp.status !== undefined) { + obj.errorIndex = resp.status; + setValuesAccountName(obj); + } + }); + } + }, 750); + }, + } as inputElementType; + const [valuesAccountName, setValuesAccountName] = useState(accountName); + + const userNameRef = useRef(setTimeout(() => {})); + + const userName = { + label: lang.account.registration.stepFinal.userName, + input: '', + errorIndex: 'none', + errorTextObject: lang.account.registration.stepFinal.userNameError, + minLength: userNameOptions.minLength, + maxLength: userNameOptions.maxLength, + isAllowed: userNameOptions.isAllowed, + isPassword: false, + onTextChange: (e: NativeSyntheticEvent) => { + let text = e.nativeEvent.text; + let self = userName; + + clearTimeout(userNameRef.current); + + userNameRef.current = setTimeout(() => { + let obj = {...valuesUserName}; + obj.input = text; + + if (text.length < self.minLength) obj.errorIndex = 'tooShort'; + else if (text.length > self.maxLength) obj.errorIndex = 'tooLong'; + else if (self.isAllowed(text) === false) obj.errorIndex = 'invalid'; + else obj.errorIndex = 'none'; + + setValuesUserName(obj); + }, 50); + }, + } as inputElementType; + const [valuesUserName, setValuesUserName] = useState(userName); + + const passwordRef = useRef(setTimeout(() => {})); + + const password = { + label: lang.account.registration.stepFinal.password, + input: '', + errorIndex: 'none', + errorTextObject: lang.account.registration.stepFinal.passwordError, + minLength: passwordOptions.minLength, + maxLength: passwordOptions.maxLength, + isAllowed: passwordOptions.isAllowed, + isPassword: true, + onTextChange: (e: NativeSyntheticEvent) => { + let text = e.nativeEvent.text; + let self = password; + + clearTimeout(passwordRef.current); + + passwordRef.current = setTimeout(() => { + let obj = {...valuesPassword}; + obj.input = text; + + if (text.length < self.minLength) obj.errorIndex = 'tooShort'; + else if (text.length > self.maxLength) obj.errorIndex = 'tooLong'; + else if (self.isAllowed(text) === false) obj.errorIndex = 'invalid'; + else obj.errorIndex = 'none'; + + setValuesPassword(obj); + + let objRe = {...valuesPasswordRe}; + if (text !== valuesPasswordRe.input) objRe.errorIndex = 'noMatch'; + else objRe.errorIndex = 'none'; + + setValuesPasswordRe(objRe); + }, 50); + }, + } as inputElementType; + const [valuesPassword, setValuesPassword] = useState(password); + + const passwordReRef = useRef(setTimeout(() => {})); + + const passwordRe = { + label: lang.account.registration.stepFinal.passwordRepeat, + input: '', + errorIndex: 'none', + errorTextObject: lang.account.registration.stepFinal.passwordError, + minLength: passwordOptions.minLength, + maxLength: passwordOptions.maxLength, + isAllowed: passwordOptions.isAllowed, + isPassword: 'passwordRepeat', + onTextChange: (e: NativeSyntheticEvent) => { + let text = e.nativeEvent.text; + let self = passwordRe; + + clearTimeout(passwordReRef.current); + + passwordReRef.current = setTimeout(() => { + let obj = {...valuesPasswordRe}; + obj.input = text; + console.log(text, valuesPassword.input); + + if (text !== valuesPassword.input) obj.errorIndex = 'noMatch'; + else obj.errorIndex = 'none'; + + setValuesPasswordRe(obj); + }, 50); + }, + } as inputElementType; + const [valuesPasswordRe, setValuesPasswordRe] = useState(passwordRe); + + const inputElement = ( + val: inputElementType, + set: React.Dispatch>, + valConst: inputElementType, + autofocus?: boolean, + ) => { + const isPassword = + val.isPassword === true || val.isPassword === 'passwordRepeat'; + + return ( + + {val.label} + setShowPassword(!showPassword)} + icon={ + + } + borderRadius="full" + /> + ) : undefined + } + /> + + }> + {val.errorTextObject[val.errorIndex] !== undefined + ? val.errorTextObject[val.errorIndex] + .replaceAll('$(minLength)', val.minLength) + .replaceAll('$(maxLength)', val.maxLength) + : val.errorTextObject[val.errorIndex] !== 'none' + ? lang.error + : null} + + + ); + }; + + return ( + + + +
+ {lang.account.registration.stepFinal.displayName} + +
+
+ {inputElement(valuesUserName, setValuesUserName, userName)} + {inputElement(valuesAccountName, setValuesAccountName, accountName)} + {inputElement(valuesPassword, setValuesPassword, password)} + {inputElement(valuesPasswordRe, setValuesPasswordRe, passwordRe)} +
+
+ + + + +
+
+ ); +} diff --git a/src/caj/components/Toast.tsx b/src/caj/components/Toast.tsx new file mode 100644 index 0000000..87e893f --- /dev/null +++ b/src/caj/components/Toast.tsx @@ -0,0 +1,142 @@ +import { + Alert, + Button, + Center, + CloseIcon, + HStack, + IconButton, + Text, + VStack, +} from 'native-base'; +import {ResponsiveValue} from 'native-base/lib/typescript/components/types'; + +const {useToast} = require('native-base'); + +const Toast = () => { + const toast = useToast(); + const ToastDetails = [ + { + title: 'Account verified', + variant: 'solid', + description: 'Thanks for signing up with us.', + isClosable: true, + }, + { + title: 'Something went wrong', + variant: 'subtle', + description: 'Please create a support ticket from the support page', + }, + { + title: 'Network connection restored', + variant: 'left-accent', + description: + 'This is to inform you that your network connectivity is restored', + isClosable: true, + }, + { + title: 'Invalid email address', + variant: 'top-accent', + description: 'Please enter a valid email address', + }, + { + title: 'Invalid email address', + variant: 'outline', + description: 'Please enter a valid email address', + }, + ]; +}; + +interface toastType { + id?: string; + status?: 'info' | (string & {}) | 'error' | 'success' | 'warning'; + variant: ResponsiveValue< + | 'subtle' + | 'solid' + | 'left-accent' + | 'top-accent' + | 'outline' + | 'outline-light' + | (string & {}) + >; + title: any; + description: any; + isClosable?: boolean; + rest: any; +} + +interface alertType extends toastType { + id: string; +} + +function showToast(toast: any, item: toastType) { + const ToastAlert = ({ + id, + status, + variant, + title, + description, + isClosable, + rest, + }: alertType) => ( + + + + + + + {title} + + + {isClosable ? ( + } + _icon={{ + color: variant === 'solid' ? 'lightText' : 'darkText', + }} + onPress={() => toast.close(id)} + /> + ) : null} + + + {description} + + + + ); + + toast.show({ + render: ({id}: {id: string}) => { + return ; + }, + }); +} + +export default showToast; diff --git a/src/caj/configs/appVar.ts b/src/caj/configs/appVar.ts index 5572010..3e6388d 100644 --- a/src/caj/configs/appVar.ts +++ b/src/caj/configs/appVar.ts @@ -1,3 +1,4 @@ +import {EMail, XToken} from '@caj/helper/types'; import {VersionType} from '@caj/helper/version'; import {APP_VERSION} from './appNonSaveVar'; @@ -43,13 +44,24 @@ export function applyUpdateChanges(appVar: any): Promise { } //these variables may be changed by the user and will be saved in storage +export interface RegisterProcess { + isRegistering: false | 'stepTwo' | 'stepFinal'; + XToken: XToken | undefined; + EMail: EMail; +} export interface PREFERENCES_VARS { version: VersionType; theme: ThemeMode; + RegisterProcess: RegisterProcess; } export const preferences_vars_default: PREFERENCES_VARS = { version: APP_VERSION, //version of datatypes in storage theme: ThemeMode.Dark, + RegisterProcess: { + isRegistering: false, + XToken: undefined, + EMail: '', + }, }; diff --git a/src/caj/configs/appVarReducer.ts b/src/caj/configs/appVarReducer.ts index c2dc1a5..527bc70 100644 --- a/src/caj/configs/appVarReducer.ts +++ b/src/caj/configs/appVarReducer.ts @@ -1,7 +1,12 @@ import {createSlice} from '@reduxjs/toolkit'; import type {PayloadAction} from '@reduxjs/toolkit'; -import {PREFERENCES_VARS, preferences_vars_default, ThemeMode} from './appVar'; +import { + PREFERENCES_VARS, + preferences_vars_default, + RegisterProcess, + ThemeMode, +} from './appVar'; import {non_save_vars, NON_SAVE_VARS} from './appNonSaveVar'; import LangFormat from '@caj/lang/default'; import {lang as defaultLang} from '@caj/lang/en'; @@ -29,6 +34,9 @@ export const appVariablesSlice = createSlice({ loadPreferences: (state, action: PayloadAction) => { state.preferences = action.payload; }, + setRegisterProcess: (state, action: PayloadAction) => { + state.preferences.RegisterProcess = action.payload; + }, }, }); diff --git a/src/caj/configs/colors.ts b/src/caj/configs/colors.ts index c9e17dc..42201dd 100644 --- a/src/caj/configs/colors.ts +++ b/src/caj/configs/colors.ts @@ -1,83 +1,194 @@ import {Platform} from 'react-native'; -import {extendTheme, useColorMode} from 'native-base'; +import {extendTheme, Theme, useColorMode} from 'native-base'; import {ThemeMode} from './appVar'; import {useSelector} from 'react-redux'; import {RootState} from '@caj/redux/store'; import {useEffect} from 'react'; -export const theme = extendTheme({ - config: { - // Changing initialColorMode to 'dark' - initialColorMode: 'dark', - }, - fonts: { - header: 'Outfit-Regular', - medium: 'Outfit-Regular', - regular: 'Outfit-Regular', - semibold: 'Outfit-Regular', - }, - colors: { - black: { - 100: '#C4C4C4', - 200: '#7C7C7C', - 300: '#292929', - 800: '#181725', +export const theme = (_theme: ThemeMode) => { + return extendTheme({ + config: { + // Changing initialColorMode to 'dark' + initialColorMode: 'dark', }, + fonts: { + header: 'Outfit-Regular', + medium: 'Outfit-Regular', + regular: 'Outfit-Regular', + semibold: 'Outfit-Regular', + }, + colors: { + black: [ + { + // darkest + 100: '#414755', + 200: '#313640', + 300: '#293037', + 400: '#282e35', + 500: '#23272e', + 600: '#1a1d22', + 700: '#19191e', + 800: '#101013', + 900: '#060607', + }, + { + //dark + 100: '#414755', + 200: '#313640', + 300: '#293037', + 400: '#282e35', + 500: '#23272e', + 600: '#1a1d22', + 700: '#19191e', + 800: '#101013', + 900: '#060607', + }, + { + //light + 900: '#e9e8f0', + 800: '#e4e3ec', + 700: '#dfdde8', + 600: '#dad8e4', + 500: '#d7d5e1', + 400: '#d4d2de', + 300: '#d2d0db', + 200: '#cfcdd8', + 100: '#cdcbd5', + }, + ][_theme], + blackBG: [ + { + // darkest + 100: '#000', + 200: '#000', + 300: '#000', + 400: '#000', + 500: '#000', + 600: '#000', + 700: '#000', + 800: '#000', + 900: '#000', + }, + { + //dark + 100: '#414755', + 200: '#313640', + 300: '#293037', + 400: '#282e35', + 500: '#23272e', + 600: '#1a1d22', + 700: '#19191e', + 800: '#101013', + 900: '#060607', + }, + { + //light + 900: '#e9e8f0', + 800: '#e4e3ec', + 700: '#dfdde8', + 600: '#dad8e4', + 500: '#d7d5e1', + 400: '#d4d2de', + 300: '#d2d0db', + 200: '#cfcdd8', + 100: '#cdcbd5', + }, + ][_theme], + white: [ + { + // darkest + 100: '#ccc', + 200: '#ccc', + 300: '#ccc', + 400: '#ccc', + 500: '#ccc', + 600: '#ccc', + 700: '#ccc', + 800: '#ccc', + 900: '#ccc', + }, + { + //dark - primary: { - 50: '#fff4f1', - 100: '#ffd6c9', - 200: '#ffb9a1', - 300: '#ff9b79', - 400: '#ff7d50', - 500: '#f96e40', - 600: '#f26030', - 700: '#e95321', - 800: '#d54b1d', - 900: '#ba4721', - }, - }, - components: { - Divider: { - baseStyle: ({colorMode}) => { - return { - backgroundColor: colorMode === 'dark' ? 'black.100' : 'black.200', - }; + 900: '#e9e8f0', + 800: '#e4e3ec', + 700: '#dfdde8', + 600: '#dad8e4', + 500: '#d7d5e1', + 400: '#d4d2de', + 300: '#d2d0db', + 200: '#cfcdd8', + 100: '#cdcbd5', + }, + { + //light + 100: '#414755', + 200: '#313640', + 300: '#293037', + 400: '#282e35', + 500: '#23272e', + 600: '#1a1d22', + 700: '#19191e', + 800: '#101013', + 900: '#060607', + }, + ][_theme], + + primary: { + 50: '#fff4f1', + 100: '#ffd6c9', + 200: '#ffb9a1', + 300: '#ff9b79', + 400: '#ff7d4f', + 500: '#f96e40', + 600: '#f26030', + 700: '#e95321', + 800: '#d54b1d', + 900: '#ba4721', }, }, - Text: { - defaultProps: { - size: 'md', - fontFamily: 'Outfit-Regular', - colorScheme: 'red', - }, - sizes: { - xl: { - fontSize: '64px', - }, - lg: { - fontSize: '32px', - }, - md: { - fontSize: '16px', - }, - sm: { - fontSize: '12px', + components: { + Divider: { + baseStyle: ({colorMode}) => { + return { + backgroundColor: colorMode === 'dark' ? 'black.100' : 'black.200', + }; }, }, - Button: { - // Can simply pass default props to change default behaviour of components. - baseStyle: { - rounded: 'md', - }, + Text: { defaultProps: { - colorScheme: 'red', + size: 'md', + fontFamily: 'Outfit-Regular', + colorScheme: 'black', + }, + sizes: { + xl: { + fontSize: '64px', + }, + lg: { + fontSize: '32px', + }, + md: { + fontSize: '16px', + }, + sm: { + fontSize: '12px', + }, + }, + Button: { + // Can simply pass default props to change default behaviour of components. + baseStyle: { + rounded: 'md', + }, + defaultProps: { + colorScheme: 'primary', + }, }, }, }, - }, -}); + }); +}; export function getBackgroundColor(tm: ThemeMode): string { switch (tm) { @@ -86,7 +197,7 @@ export function getBackgroundColor(tm: ThemeMode): string { case ThemeMode.Darkest: return '#000'; default: - return '#282f34'; + return '#1b1c22'; } } @@ -117,3 +228,26 @@ export function ThemeSwitcher() { return null; } + +export const defaultHeaderStyle = (cur: ThemeMode, place?: 'registration') => { + if (place === 'registration') { + return { + headerTintColor: '#fff', + headerStyle: { + backgroundColor: theme(cur).colors.primary[500], + }, + }; + } + + return { + headerTintColor: theme(cur).colors.primary[400], + headerStyle: { + backgroundColor: + cur === ThemeMode.Light + ? '#eee' + : cur === ThemeMode.Dark + ? '#181d21' + : '#000', + }, + }; +}; diff --git a/src/caj/helper/animations.tsx b/src/caj/helper/animations.tsx new file mode 100644 index 0000000..395ac34 --- /dev/null +++ b/src/caj/helper/animations.tsx @@ -0,0 +1,132 @@ +import {useFocusEffect} from '@react-navigation/native'; +import {animated, useSpring} from '@react-spring/native'; +import {useEffect} from 'react'; +import {Dimensions, View} from 'react-native'; + +const AnimationView = animated(View); + +export const FadeInView = (props: any) => { + const [motionProps, api] = useSpring( + () => ({ + from: { + opacity: 0, + }, + }), + [], + ); + + useFocusEffect(() => { + api.start({ + to: [ + { + opacity: 1, + }, + ], + }); + return () => { + api.start({ + to: [ + { + opacity: 0, + }, + ], + }); + }; + }); + + return ( + + {props.children} + + ); +}; + +export const SlideFromLeftView = (props: any) => { + let SCREEN_WIDTH = Dimensions.get('window').width; + + const [motionProps, api] = useSpring( + () => ({ + from: { + translateX: -SCREEN_WIDTH, + }, + }), + [], + ); + + useFocusEffect(() => { + api.start({ + to: [ + { + translateX: 0, + }, + ], + }); + return () => { + SCREEN_WIDTH = Dimensions.get('window').width; + api.start({ + to: [ + { + translateX: -SCREEN_WIDTH, + }, + ], + }); + }; + }); + + return ( + + {props.children} + + ); +}; + +export const SlideFromRightView = (props: any) => { + let SCREEN_WIDTH = Dimensions.get('window').width; + + const [motionProps, api] = useSpring( + () => ({ + from: { + translateX: SCREEN_WIDTH, + }, + }), + [], + ); + + useFocusEffect(() => { + api.start({ + to: [ + { + translateX: 0, + }, + ], + }); + return () => { + SCREEN_WIDTH = Dimensions.get('window').width; + api.start({ + to: [ + { + translateX: SCREEN_WIDTH, + }, + ], + }); + }; + }); + + return ( + + {props.children} + + ); +}; diff --git a/src/caj/helper/request.ts b/src/caj/helper/request.ts new file mode 100644 index 0000000..2b9f50f --- /dev/null +++ b/src/caj/helper/request.ts @@ -0,0 +1,168 @@ +import {types} from '@babel/core'; +import {store} from '@caj/redux/store'; +import {Any} from '@react-spring/types'; +import {type} from 'os'; +import {Platform} from 'react-native'; + +import getUserAgent from './userAgent'; + +import { + AccountName, + EMail, + Password, + SessionId, + UserId, + Username, + verifyId, + WebSocketSessionId, + XToken, +} from './types'; + +export const apiPath = { + backend: { + prefix: 'https://alpha-api.clickandjoin.umbach.dev/v1', + }, +}; + +export enum apiBackendRequest { + REGISTER_STEP_1 = '/admin/users/email', + REGISTER_RESEND_MAIL = '/admin/users/email/resend', + REGISTER_STEP_2 = '/verify/email/:xToken/:verifyId', + REGISTER_STEP_FINAL = '/admin/users', + REGISTER_STEP_FINAL_ACCOUNT_NAME_CHECK = '/admin/users/validation/accountname', +} + +type requestGET = {[key: string]: string}; + +export interface defaultRequest { + status?: 200 | 400 | 422 | 500; + error?: string; + requestGET?: requestGET; +} + +interface REGISTER_STEP_1 extends defaultRequest { + path: apiBackendRequest.REGISTER_STEP_1; + requestHeader: {}; + request: { + Email: EMail; + }; + response: { + XToken: XToken | undefined; + }; +} +interface REGISTER_RESEND_MAIL extends defaultRequest { + path: apiBackendRequest.REGISTER_RESEND_MAIL; + requestHeader: {}; + request: { + Email: EMail; + }; + // response: { + // XToken: XToken | undefined; + //}; +} + +interface REGISTER_STEP_2 extends defaultRequest { + path: apiBackendRequest.REGISTER_STEP_2; + requestHeader: {}; + requestGET: { + ':xToken': XToken; + ':verifyId': verifyId; + }; +} + +interface REGISTER_STEP_FINAL_ACCOUNT_NAME_CHECK extends defaultRequest { + path: apiBackendRequest.REGISTER_STEP_FINAL_ACCOUNT_NAME_CHECK; + + request: { + AccountName: AccountName; + }; + // response: { + // XToken: XToken | undefined; + //}; +} + +interface REGISTER_STEP_FINAL extends defaultRequest { + path: apiBackendRequest.REGISTER_STEP_FINAL; + requestHeader: { + XToken: XToken; + }; + request: { + AccountName: AccountName; + Username: Username; + Password: Password; + }; + response?: { + SessionId: SessionId; + UserId: UserId; + WebSocketSessionId: WebSocketSessionId; + }; +} + +type FetchTypes = + | REGISTER_STEP_1 + | REGISTER_RESEND_MAIL + | REGISTER_STEP_2 + | REGISTER_STEP_FINAL + | REGISTER_STEP_FINAL_ACCOUNT_NAME_CHECK; + +function isA(obj: any): obj is REGISTER_STEP_1 { + return obj.request !== undefined; +} + +export function makeRequest(type: T1): Promise { + let makeRequestObj: any = type; + + let path = type.path.toString(); + + if (type.requestGET !== undefined) { + for (let key in type.requestGET) { + path = path.replaceAll(key, (type.requestGET as requestGET)[key]); + } + } + + // Simple POST request with a JSON body using fetch + + let headers: HeadersInit = { + 'Content-Type': 'application/json', + 'X-App-Version': store + .getState() + .nonSaveVariables.currentAppVersion.toString(), + 'X-Language': store.getState().appVariables.lang.details.langCode, + }; + + if (Platform.OS === 'android' || Platform.OS === 'ios') + headers['User-Agent'] = getUserAgent(); + + const requestOptions: RequestInit = { + method: makeRequestObj.request !== undefined ? 'POST' : 'GET', + + headers, + body: + makeRequestObj.request !== undefined + ? JSON.stringify(makeRequestObj.request) + : undefined, + }; + + return new Promise((resolve, reject) => { + fetch(apiPath.backend.prefix + path, requestOptions) + .then(response => { + makeRequestObj.status = response.status; + + if (makeRequestObj.response === undefined) return {}; + + return response.json(); + }) + .then(data => { + makeRequestObj.response = data; + + //console.log(makeRequestObj); + if (makeRequestObj.status !== 200) reject(makeRequestObj); + else resolve(makeRequestObj); + }) + .catch(error => { + //console.error(error, makeRequestObj); + makeRequestObj.error = error; + reject(makeRequestObj); + }); + }); +} diff --git a/src/caj/helper/types.ts b/src/caj/helper/types.ts new file mode 100644 index 0000000..59fc160 --- /dev/null +++ b/src/caj/helper/types.ts @@ -0,0 +1,44 @@ +export type EMail = string; + +export type AccountName = string; +export type Username = string; +export type Password = string; +export type UserAgent = string; +export type XToken = string; +export type verifyId = string; + +export type SessionId = string; +export type UserId = string; +export type WebSocketSessionId = string; + +export const accountNameOptions = { + minLength: 4, + maxLength: 24, + isAllowed: (text: string): boolean => { + return text.match('^[a-zA-Z0-9_.]+$') !== null; + }, +}; +export const userNameOptions = { + minLength: 2, + maxLength: 24, + isAllowed: (text: string): boolean => { + return true; + }, +}; + +export const passwordOptions = { + minLength: 6, + maxLength: 64, + isAllowed: (text: string): boolean => { + return true; + }, +}; + +export const emailOptions = { + maxLength: 48, + isAllowed: (email: string) => { + return String(email).match( + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, + ); + }, +}; diff --git a/src/caj/helper/userAgent.ts b/src/caj/helper/userAgent.ts new file mode 100644 index 0000000..5a0c8e3 --- /dev/null +++ b/src/caj/helper/userAgent.ts @@ -0,0 +1,3 @@ +import {getUserAgent} from 'react-native-user-agent'; + +export default getUserAgent; diff --git a/src/caj/helper/userAgent.web.ts b/src/caj/helper/userAgent.web.ts new file mode 100644 index 0000000..84e5410 --- /dev/null +++ b/src/caj/helper/userAgent.web.ts @@ -0,0 +1,2 @@ +const getUserAgent = undefined; +export default getUserAgent; diff --git a/src/caj/lang/default.ts b/src/caj/lang/default.ts index 7b8c58f..d27acfb 100644 --- a/src/caj/lang/default.ts +++ b/src/caj/lang/default.ts @@ -9,7 +9,56 @@ export default interface LangFormat { details: LangDetails; curVersion: string; appName: string; + appNameDesc: string; startHelper1: string; + navigation: { + home: { + account: string; + calendar: string; + chat: string; + }; + }; + info: string; + error: string; + success: string; + account: { + registration: { + registration: string; + info: string; + privacyPolicy: string; + termsOfUse: string; + stepOne: { + title: string; + success: string; + addressExists: string; + addressInvalid: string; + noMailEntered: string; + button: string; + }; + stepTwo: { + title: string; + verification: string; + resend: [string, string, string]; + noCodeEntered: string; + resendError: {[key: number]: string}; + verificationError: {[key: number]: string}; + button: string; + success: string; + }; + stepFinal: { + verification: string; + userName: string; + accountName: string; + password: string; + passwordRepeat: string; + displayName: string; + accountNameError: {[key: string]: string}; + userNameError: {[key: string]: string}; + passwordError: {[key: string]: string}; + button: string; + }; + }; + }; } interface LangPlaceholderKeys { diff --git a/src/caj/lang/en.ts b/src/caj/lang/en.ts index 4076eaf..d8d60c5 100644 --- a/src/caj/lang/en.ts +++ b/src/caj/lang/en.ts @@ -8,5 +8,85 @@ export const lang: LangFormat = { }, curVersion: 'Your current version is v${version}.', appName: 'Click And Join', + appNameDesc: 'an app developed with love by Janex', startHelper1: 'Your data will be loaded :)', + navigation: { + home: { + account: 'My account', + calendar: 'Calendar', + chat: 'Conversations', + }, + }, + info: 'Info', + error: 'Error', + success: 'Success', + account: { + registration: { + registration: 'Registration', + info: 'By registering, you agree to our ${TermsOfUse}. You can find out how we collect and use your data in our ${privacyPolicy}.', + privacyPolicy: 'privacy policy', + termsOfUse: 'Terms of Use', + stepOne: { + title: 'Enter E-Mail', + success: 'A verification has sent to your E-Mail!', + addressExists: 'The E-Mail you entered is already in use.', + addressInvalid: 'The address you entered has an invalid format.', + noMailEntered: 'Please enter your E-Mail', + button: 'Next step', + }, + stepTwo: { + title: 'Enter the 6-digit code that we sent you to ${EMail}.', + verification: 'X-X-X-X-X-X', + noCodeEntered: 'Please enter your code', + resend: [ + 'E-Mail not arrived? You can also resend the verification E-Mail by ${resend}.', + 'clicking here', + 'E-Mail verification has resent', + ], + resendError: { + 400: 'The E-Mail is already verified!', + 401: 'Your device have changed. Please use another E-Mail address.', + 429: 'Too many requests in a too small period of time in a row', + }, + verificationError: { + 400: 'Something went wrong please try again :( Restart App maybe required', + 401: 'The code you entered does not match with the code we sent you', + 422: 'The verification time is expired. Please please try again', + }, + button: 'Verify', + success: 'Verification success!', + }, + stepFinal: { + verification: + 'Your Account has been verified. Now enter your user credentials.', + userName: 'Username', + accountName: 'AccountName', + password: 'Password', + passwordRepeat: 'Repeat password', + displayName: 'Other users see you like this:', + accountNameError: { + tooLong: 'Too long. Max length are $(maxLength) character.', + tooShort: 'Too short. Min length are $(minLength) character.', + required: 'This field is required', + invalid: + 'Account names can only contain letters, numbers, underscores (_) and dots (.)', + exists: 'The account name you entered already exists.', + }, + userNameError: { + tooLong: 'Too long. Max length are $(maxLength) character.', + tooShort: 'Too short. Min length are $(minLength) character.', + required: 'This field is required', + }, + passwordError: { + noMatch: 'Passwords do not match', + tooLong: 'Too long. Max length are $(maxLength) character.', + tooShort: 'Too short. Min length are $(minLength) character.', + required: 'This field is required', + weak: 'Password is too weak', + }, + + button: 'Finish registration', + }, + }, + }, }; diff --git a/src/caj/tabs/main/AccountTab.tsx b/src/caj/tabs/main/AccountTab.tsx new file mode 100644 index 0000000..df1a73c --- /dev/null +++ b/src/caj/tabs/main/AccountTab.tsx @@ -0,0 +1,71 @@ +import AccountInfoBanner from '@caj/components/AccountInfoBanner'; +import NotLoggedIn from '@caj/components/NotLoggedIn'; +import {ThemeMode} from '@caj/configs/appVar'; +import {defaultHeaderStyle} from '@caj/configs/colors'; +import {RootStackNavigatorParamList} from '@caj/Navigation'; +import {RootState} from '@caj/redux/store'; +import { + createNativeStackNavigator, + NativeStackNavigationProp, +} from '@react-navigation/native-stack'; +import {Box, Center, Container, Text} from 'native-base'; +import {useSelector} from 'react-redux'; + +export const AccountTabName = 'Account'; + +export type AccountStackNavigatorParamList = { + Overview: undefined; +}; + +const AccountStack = + createNativeStackNavigator(); + +export type AccountScreenNavigationProp = + NativeStackNavigationProp; + +function AccountTab() { + const lang = useSelector((state: RootState) => state.appVariables.lang); + const theme = useSelector( + (state: RootState) => state.appVariables.preferences.theme, + ); + + return ( + + + + ); +} + +function AccountScreen() { + const theme = useSelector( + (state: RootState) => state.appVariables.preferences.theme, + ); + return ( + <> + + + + + + ); +} + +export default AccountTab; diff --git a/src/caj/tabs/main/CalendarTab.tsx b/src/caj/tabs/main/CalendarTab.tsx new file mode 100644 index 0000000..0d0d2a8 --- /dev/null +++ b/src/caj/tabs/main/CalendarTab.tsx @@ -0,0 +1,53 @@ +import NotLoggedIn from '@caj/components/NotLoggedIn'; +import {defaultHeaderStyle} from '@caj/configs/colors'; +import {RootStackNavigatorParamList} from '@caj/Navigation'; +import {RootState} from '@caj/redux/store'; +import { + createNativeStackNavigator, + NativeStackNavigationProp, +} from '@react-navigation/native-stack'; +import {Center, Text} from 'native-base'; +import {useSelector} from 'react-redux'; + +export const CalendarTabName = 'Calendar'; + +export type CalendarStackNavigatorParamList = { + Overview: undefined; +}; + +const CalendarStack = + createNativeStackNavigator(); + +export type CalendarScreenNavigationProp = + NativeStackNavigationProp; + +function CalendarTab() { + const lang = useSelector((state: RootState) => state.appVariables.lang); + const theme = useSelector( + (state: RootState) => state.appVariables.preferences.theme, + ); + + return ( + + + + ); +} + +function CalendarScreen() { + return ( +
+ +
+ ); +} + +export default CalendarTab; diff --git a/src/caj/tabs/main/ChatTab.tsx b/src/caj/tabs/main/ChatTab.tsx new file mode 100644 index 0000000..1534926 --- /dev/null +++ b/src/caj/tabs/main/ChatTab.tsx @@ -0,0 +1,52 @@ +import NotLoggedIn from '@caj/components/NotLoggedIn'; +import {defaultHeaderStyle} from '@caj/configs/colors'; +import {RootStackNavigatorParamList} from '@caj/Navigation'; +import {RootState} from '@caj/redux/store'; +import { + createNativeStackNavigator, + NativeStackNavigationProp, +} from '@react-navigation/native-stack'; +import {Center, Text} from 'native-base'; +import {useSelector} from 'react-redux'; + +export const ChatTabName = 'Chat'; + +export type ChatStackNavigatorParamList = { + Overview: undefined; +}; + +const ChatStack = createNativeStackNavigator(); + +export type ChatScreenNavigationProp = + NativeStackNavigationProp; + +function ChatTab() { + const lang = useSelector((state: RootState) => state.appVariables.lang); + const theme = useSelector( + (state: RootState) => state.appVariables.preferences.theme, + ); + + return ( + + + + ); +} + +function ChatScreen() { + return ( +
+ +
+ ); +} + +export default ChatTab; diff --git a/src/caj/tabs/main/MapsTab.tsx b/src/caj/tabs/main/MapsTab.tsx new file mode 100644 index 0000000..3c0768e --- /dev/null +++ b/src/caj/tabs/main/MapsTab.tsx @@ -0,0 +1,47 @@ +import {defaultHeaderStyle} from '@caj/configs/colors'; +import {RootStackNavigatorParamList} from '@caj/Navigation'; +import {RootState} from '@caj/redux/store'; +import { + createNativeStackNavigator, + NativeStackNavigationProp, +} from '@react-navigation/native-stack'; +import {Center, Text} from 'native-base'; +import {useSelector} from 'react-redux'; + +export const MapsTabName = 'Maps'; + +export type MapsStackNavigatorParamList = { + Overview: undefined; +}; + +const MapsStack = createNativeStackNavigator(); + +export type MapsScreenNavigationProp = + NativeStackNavigationProp; + +function MapsTab() { + const lang = useSelector((state: RootState) => state.appVariables.lang); + const theme = useSelector( + (state: RootState) => state.appVariables.preferences.theme, + ); + + return ( + + + + ); +} + +function MapsScreen() { + return
MapsScreen
; +} + +export default MapsTab; diff --git a/web/public/index.html b/web/public/index.html index 2dce563..c760e59 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -12,52 +12,6 @@ --doc--height: 100%; } - @font-face { - font-family: 'Outfit-Thin'; - src: url('/fonts/Outfit.ttf'); - font-weight: 100; - } - @font-face { - font-family: 'Outfit-ExtraLight'; - src: url('/fonts/Outfit.ttf'); - font-weight: 200; - } - @font-face { - font-family: 'Outfit-Light'; - src: url('/fonts/Outfit.ttf'); - font-weight: 300; - } - @font-face { - font-family: 'Outfit-Regular'; - src: url('/fonts/Outfit.ttf'); - font-weight: 400; - } - @font-face { - font-family: 'Outfit-Medium'; - src: url('/fonts/Outfit.ttf'); - font-weight: 500; - } - @font-face { - font-family: 'Outfit-SemiBold'; - src: url('/fonts/Outfit.ttf'); - font-weight: 600; - } - @font-face { - font-family: 'Outfit-Bold'; - src: url('/fonts/Outfit.ttf'); - font-weight: 700; - } - @font-face { - font-family: 'Outfit-ExtraBold'; - src: url('/fonts/Outfit.ttf'); - font-weight: 800; - } - @font-face { - font-family: 'Outfit-Black'; - src: url('/fonts/Outfit.ttf'); - font-weight: 900; - } - html, body { padding: 0; diff --git a/web/webpack.config.js b/web/webpack.config.js index 32ba0d9..d9f0837 100644 --- a/web/webpack.config.js +++ b/web/webpack.config.js @@ -11,13 +11,14 @@ const appDirectory = path.resolve(__dirname, '../'); // errors. To fix this webpack can be configured to compile to the necessary // `node_module`. const babelLoaderConfiguration = { - test: /\.(ts|tsx|js)?$/, + test: /\.(ts|tsx|js|jsx)?$/, // Add every directory that needs to be compiled by Babel during the build. include: [ path.resolve(appDirectory, 'index.web.tsx'), path.resolve(appDirectory, 'src'), + /node_modules\/react-native-/, ], - exclude: [path.resolve(appDirectory, 'node_modules')], + // exclude: [path.resolve(appDirectory, 'node_modules')], use: { loader: 'babel-loader', options: { @@ -25,14 +26,20 @@ const babelLoaderConfiguration = { // The 'react-native' preset is recommended to match React Native's packager // presets: ['module:metro-react-native-babel-preset'], // presets: ['react-native'], - presets: [require.resolve('babel-preset-react-native')], - // Re-write paths to import only the modules needed by the app - plugins: ['react-native-web'], - presets: ['react-native'], + presets: ['module:metro-react-native-babel-preset'], plugins: [ // needed to support async/await - '@babel/plugin-transform-runtime', + // '@babel/plugin-transform-runtime', + 'react-native-web', + [ + 'module-resolver', + { + alias: { + '^react-native$': 'react-native-web', + }, + }, + ], ], }, }, @@ -49,9 +56,26 @@ const imageLoaderConfiguration = { }, }; +const ttfLoaderConfiguration = { + test: /\.ttf$/, + loader: 'url-loader', // or directly file-loader + include: [ + path.resolve(appDirectory, 'node_modules/react-native-vector-icons'), + path.resolve(appDirectory, 'web/public/fonts'), + ], +}; + module.exports = { mode: 'development', target: 'web', + devServer: { + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', + 'Access-Control-Allow-Headers': + 'X-Requested-With, content-type, Authorization', + }, + }, entry: [ // load any web API polyfills // path.resolve(appDirectory, 'polyfills-web.js'), @@ -68,7 +92,11 @@ module.exports = { // ...the rest of your config module: { - rules: [babelLoaderConfiguration, imageLoaderConfiguration], + rules: [ + babelLoaderConfiguration, + imageLoaderConfiguration, + ttfLoaderConfiguration, + ], }, resolve: { @@ -80,6 +108,15 @@ module.exports = { // If you're working on a multi-platform React Native app, web-specific // module implementations should be written in files using the extension // `.web.js`. - extensions: ['.web.js', '.js', '.web.ts', '.ts', '.web.tsx', '.tsx'], + extensions: [ + '.web.js', + '.web.jsx', + '.js', + '.jsx', + '.web.ts', + '.ts', + '.web.tsx', + '.tsx', + ], }, };