From 5fec50605d672c7737412ba68c4ce93cc8d80b2f Mon Sep 17 00:00:00 2001 From: victorbalssa Date: Wed, 10 Aug 2022 14:40:03 -0700 Subject: [PATCH 1/8] [UPDATE] Use redux hooks :fire:. --- App.tsx | 9 - README.md | 2 +- app.json | 10 +- index.js | 2 +- package-lock.json | 166 ++++++++++++- package.json | 5 +- src/{native => }/components/Chart.tsx | 17 +- .../components/Charts/AssetsHistoryChart.tsx | 2 +- src/components/Configuration.tsx | 103 ++++++++ src/{native => }/components/Home.tsx | 4 +- src/{native => }/components/Oauth.tsx | 22 +- .../components/Transactions/Create.tsx | 0 .../components/Transactions/Edit.tsx | 4 +- .../components/Transactions/Form.tsx | 0 .../components/Transactions/List.tsx | 32 +-- src/{native => }/components/UI/Loading.tsx | 0 src/components/UI/RangeTitle.tsx | 84 +++++++ src/{native => }/components/UI/Title.tsx | 0 src/{native => }/components/UI/ToastAlert.tsx | 0 src/containers/Chart.tsx | 68 ++--- src/containers/Configuration.tsx | 47 +--- src/containers/Home.tsx | 64 ++--- src/containers/Oauth.tsx | 83 ++----- src/containers/Transactions/Create.tsx | 89 ++----- src/containers/Transactions/Edit.tsx | 54 ++-- src/containers/Transactions/List.tsx | 38 +-- src/index.tsx | 202 +++++++++++++++ src/models/firefly.ts | 17 +- src/native/components/Configuration.tsx | 108 -------- src/native/components/UI/RangeTitle.tsx | 96 -------- src/native/components/UI/UIButton.tsx | 70 ------ src/native/index.tsx | 232 ------------------ src/{native => }/routes/index.tsx | 16 +- src/store/index.ts | 2 +- 34 files changed, 731 insertions(+), 917 deletions(-) delete mode 100644 App.tsx rename src/{native => }/components/Chart.tsx (72%) rename src/{native => }/components/Charts/AssetsHistoryChart.tsx (99%) create mode 100644 src/components/Configuration.tsx rename src/{native => }/components/Home.tsx (98%) rename src/{native => }/components/Oauth.tsx (84%) rename src/{native => }/components/Transactions/Create.tsx (100%) rename src/{native => }/components/Transactions/Edit.tsx (96%) rename src/{native => }/components/Transactions/Form.tsx (100%) rename src/{native => }/components/Transactions/List.tsx (93%) rename src/{native => }/components/UI/Loading.tsx (100%) create mode 100644 src/components/UI/RangeTitle.tsx rename src/{native => }/components/UI/Title.tsx (100%) rename src/{native => }/components/UI/ToastAlert.tsx (100%) create mode 100755 src/index.tsx delete mode 100644 src/native/components/Configuration.tsx delete mode 100644 src/native/components/UI/RangeTitle.tsx delete mode 100644 src/native/components/UI/UIButton.tsx delete mode 100755 src/native/index.tsx rename src/{native => }/routes/index.tsx (92%) diff --git a/App.tsx b/App.tsx deleted file mode 100644 index 1320f22..0000000 --- a/App.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import Root from './src/native/index'; -import { store, persistor } from './src/store'; - -export default function App() { - return ( - - ); -} diff --git a/README.md b/README.md index 959db32..b459019 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

-

Abacus: a FireflyIII iOS Client

+

🔥 Abacus: Firefly III iOS app 🔥

diff --git a/app.json b/app.json index 10dea97..58e8d00 100644 --- a/app.json +++ b/app.json @@ -1,7 +1,7 @@ { "expo": { "name": "Abacus", - "description": "Abacus: a FireflyIII iOS Client", + "description": "🔥 Abacus: Firefly III iOS app 🔥", "slug": "abacus", "privacy": "hidden", "platforms": [ @@ -16,7 +16,8 @@ }, "updates": { "enabled": true, - "checkAutomatically": "ON_ERROR_RECOVERY" + "checkAutomatically": "ON_ERROR_RECOVERY", + "url": "https://u.expo.dev/292ed6dc-804c-4444-95f5-fa5d76d9913b" }, "ios": { "buildNumber": "0.2.1", @@ -29,6 +30,9 @@ } }, "scheme": "abacusiosapp", - "githubUrl": "https://github.com/victorbalssa/abacus" + "githubUrl": "https://github.com/victorbalssa/abacus", + "runtimeVersion": { + "policy": "sdkVersion" + } } } diff --git a/index.js b/index.js index 5fd059f..373114b 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ import { registerRootComponent } from 'expo'; -import App from './App'; +import App from './src'; registerRootComponent(App); diff --git a/package-lock.json b/package-lock.json index 78e9c27..d4086f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "expo": "^46.0.0", "expo-asset": "~8.6.1", "expo-auth-session": "~3.7.1", + "expo-dev-client": "~1.1.1", "expo-font": "~10.2.0", "expo-haptics": "~11.3.0", "expo-linear-gradient": "~11.4.0", @@ -43,7 +44,7 @@ "react": "18.0.0", "react-dom": "18.0.0", "react-helmet": "^5.2.1", - "react-native": "0.69.3", + "react-native": "0.69.4", "react-native-animated-splash-screen": "^2.0.5", "react-native-gesture-handler": "~2.5.0", "react-native-modal": "^11.5.6", @@ -14679,6 +14680,85 @@ "expo": "*" } }, + "node_modules/expo-dev-client": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-1.1.1.tgz", + "integrity": "sha512-62/QX+tfdGfth4XKtE0zYUHPlW1sEWbY+aFLk0Wt3xZF0Y2tb1I6gAU68X3nSKNueZDPOAgjfG2uyyyOEgq9mw==", + "dependencies": { + "@expo/config-plugins": "~5.0.0", + "expo-dev-launcher": "1.1.1", + "expo-dev-menu": "1.1.1", + "expo-dev-menu-interface": "0.7.1", + "expo-manifests": "~0.3.0", + "expo-updates-interface": "~0.7.0" + }, + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-dev-launcher": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-1.1.1.tgz", + "integrity": "sha512-bZyhcVKI3r+d46wx3CfF5mG3fn01/mpUeDEcqfAOp9lEV0/NijbBiA+WzdHyK+mGf8z3igI7g7BuOyCYGLo9nw==", + "dependencies": { + "@expo/config-plugins": "~5.0.0", + "expo-dev-menu": "1.1.1", + "resolve-from": "^5.0.0", + "semver": "^7.3.5" + }, + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-dev-launcher/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/expo-dev-menu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-1.1.1.tgz", + "integrity": "sha512-13YOkWmhvQVHuKRJsPIpdo2yWxr+mPneENu5VHsBvAL9WKVh+4KwAMJ35YM7LAoV0VCxxZmcehIQ30koC54DZg==", + "dependencies": { + "@expo/config-plugins": "~5.0.0", + "expo-dev-menu-interface": "0.7.1", + "semver": "^7.3.5" + }, + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-dev-menu-interface": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/expo-dev-menu-interface/-/expo-dev-menu-interface-0.7.1.tgz", + "integrity": "sha512-EeG6ymydrr/DWy83wQY4uSToVAVLYeOIXc8UH00cpWqYTQRUK7BAuloEItW9/nOrRXJDU6nalSOje5n8kC/BgA==", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-dev-menu/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/expo-eas-client": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/expo-eas-client/-/expo-eas-client-0.3.0.tgz", @@ -31201,14 +31281,14 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-native": { - "version": "0.69.3", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.3.tgz", - "integrity": "sha512-SyGkcoEUa/BkO+wKVnv4OsnLSNfUM5zLNXS3iT/3eXjKX91/FKBH/nfR9BE1c60X5LQe/P5QYqr6WPX3TRSQkA==", + "version": "0.69.4", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.4.tgz", + "integrity": "sha512-rqNMialM/T4pHRKWqTIpOxA65B/9kUjtnepxwJqvsdCeMP9Q2YdSx4VASFR9RoEFYcPRU41yGf6EKrChNfns3g==", "dependencies": { "@jest/create-cache-key-function": "^27.0.1", - "@react-native-community/cli": "^8.0.3", - "@react-native-community/cli-platform-android": "^8.0.2", - "@react-native-community/cli-platform-ios": "^8.0.2", + "@react-native-community/cli": "^8.0.4", + "@react-native-community/cli-platform-android": "^8.0.4", + "@react-native-community/cli-platform-ios": "^8.0.4", "@react-native/assets": "1.0.0", "@react-native/normalize-color": "2.0.0", "@react-native/polyfills": "2.0.0", @@ -53439,6 +53519,66 @@ "integrity": "sha512-EH1Ikcy/HxfLJpo+zVRic4Igl6AovZEksAheKfeM2u+2TfL3FEBiQo+cGkuIa8NQ9ui3xYqQcyMk+IIZ6AO0Xg==", "requires": {} }, + "expo-dev-client": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-1.1.1.tgz", + "integrity": "sha512-62/QX+tfdGfth4XKtE0zYUHPlW1sEWbY+aFLk0Wt3xZF0Y2tb1I6gAU68X3nSKNueZDPOAgjfG2uyyyOEgq9mw==", + "requires": { + "@expo/config-plugins": "~5.0.0", + "expo-dev-launcher": "1.1.1", + "expo-dev-menu": "1.1.1", + "expo-dev-menu-interface": "0.7.1", + "expo-manifests": "~0.3.0", + "expo-updates-interface": "~0.7.0" + } + }, + "expo-dev-launcher": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-1.1.1.tgz", + "integrity": "sha512-bZyhcVKI3r+d46wx3CfF5mG3fn01/mpUeDEcqfAOp9lEV0/NijbBiA+WzdHyK+mGf8z3igI7g7BuOyCYGLo9nw==", + "requires": { + "@expo/config-plugins": "~5.0.0", + "expo-dev-menu": "1.1.1", + "resolve-from": "^5.0.0", + "semver": "^7.3.5" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "expo-dev-menu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-1.1.1.tgz", + "integrity": "sha512-13YOkWmhvQVHuKRJsPIpdo2yWxr+mPneENu5VHsBvAL9WKVh+4KwAMJ35YM7LAoV0VCxxZmcehIQ30koC54DZg==", + "requires": { + "@expo/config-plugins": "~5.0.0", + "expo-dev-menu-interface": "0.7.1", + "semver": "^7.3.5" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "expo-dev-menu-interface": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/expo-dev-menu-interface/-/expo-dev-menu-interface-0.7.1.tgz", + "integrity": "sha512-EeG6ymydrr/DWy83wQY4uSToVAVLYeOIXc8UH00cpWqYTQRUK7BAuloEItW9/nOrRXJDU6nalSOje5n8kC/BgA==", + "requires": {} + }, "expo-eas-client": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/expo-eas-client/-/expo-eas-client-0.3.0.tgz", @@ -66377,14 +66517,14 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-native": { - "version": "0.69.3", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.3.tgz", - "integrity": "sha512-SyGkcoEUa/BkO+wKVnv4OsnLSNfUM5zLNXS3iT/3eXjKX91/FKBH/nfR9BE1c60X5LQe/P5QYqr6WPX3TRSQkA==", + "version": "0.69.4", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.4.tgz", + "integrity": "sha512-rqNMialM/T4pHRKWqTIpOxA65B/9kUjtnepxwJqvsdCeMP9Q2YdSx4VASFR9RoEFYcPRU41yGf6EKrChNfns3g==", "requires": { "@jest/create-cache-key-function": "^27.0.1", - "@react-native-community/cli": "^8.0.3", - "@react-native-community/cli-platform-android": "^8.0.2", - "@react-native-community/cli-platform-ios": "^8.0.2", + "@react-native-community/cli": "^8.0.4", + "@react-native-community/cli-platform-android": "^8.0.4", + "@react-native-community/cli-platform-ios": "^8.0.4", "@react-native/assets": "1.0.0", "@react-native/normalize-color": "2.0.0", "@react-native/polyfills": "2.0.0", diff --git a/package.json b/package.json index aaef5ed..ed7637f 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "react": "18.0.0", "react-dom": "18.0.0", "react-helmet": "^5.2.1", - "react-native": "0.69.3", + "react-native": "0.69.4", "react-native-animated-splash-screen": "^2.0.5", "react-native-gesture-handler": "~2.5.0", "react-native-modal": "^11.5.6", @@ -66,7 +66,8 @@ "sharp-cli": "^1.14.1", "slash": "^3.0.0", "victory-native": "^36.3.1", - "expo-local-authentication": "~12.3.0" + "expo-local-authentication": "~12.3.0", + "expo-dev-client": "~1.1.1" }, "devDependencies": { "@babel/core": "^7.18.6", diff --git a/src/native/components/Chart.tsx b/src/components/Chart.tsx similarity index 72% rename from src/native/components/Chart.tsx rename to src/components/Chart.tsx index d1978bd..79d919e 100644 --- a/src/native/components/Chart.tsx +++ b/src/components/Chart.tsx @@ -6,20 +6,7 @@ import { } from 'native-base'; import AssetsHistoryChart from './Charts/AssetsHistoryChart'; import RangeTitle from './UI/RangeTitle'; -import colors from '../../constants/colors'; -import { AssetAccountType } from '../../models/firefly'; - -type ChartType = { - accounts: AssetAccountType[], - loading: boolean, - fetchData: () => Promise, - start: string, - end: string, - filterData: () => Promise, - enableScroll: () => Promise, - disableScroll: () => Promise, - scrollEnabled: boolean, -} +import colors from '../constants/colors'; const Chart = ({ accounts, @@ -31,7 +18,7 @@ const Chart = ({ enableScroll, disableScroll, scrollEnabled, -}: ChartType) => ( +}) => ( <> diff --git a/src/native/components/Charts/AssetsHistoryChart.tsx b/src/components/Charts/AssetsHistoryChart.tsx similarity index 99% rename from src/native/components/Charts/AssetsHistoryChart.tsx rename to src/components/Charts/AssetsHistoryChart.tsx index de86b7d..822c03f 100644 --- a/src/native/components/Charts/AssetsHistoryChart.tsx +++ b/src/components/Charts/AssetsHistoryChart.tsx @@ -15,7 +15,7 @@ import { } from 'victory-native'; import { maxBy, minBy } from 'lodash'; import { Line, Circle } from 'react-native-svg'; -import colors from '../../../constants/colors'; +import colors from '../../constants/colors'; const CursorPointer = ({ x, diff --git a/src/components/Configuration.tsx b/src/components/Configuration.tsx new file mode 100644 index 0000000..8108c8e --- /dev/null +++ b/src/components/Configuration.tsx @@ -0,0 +1,103 @@ +import React from 'react'; +import { + Box, + Stack, + Text, + HStack, + Heading, + Switch, AlertDialog, Button, +} from 'native-base'; +import * as Linking from 'expo-linking'; +import { AntDesign } from '@expo/vector-icons'; +import { ScrollView, View } from 'react-native'; + +const Configuration = ({ + faceId, + setFaceId, + resetApp, + backendURL, +}) => { + const [isResetOpen, setResetOpen] = React.useState(false); + const onResetClose = () => setResetOpen(false); + const ResetRef = React.useRef(null); + + return ( + + + Security + + + URL + Linking.openURL(backendURL)} underline>{backendURL} + + + Help + Linking.openURL('https://github.com/victorbalssa/abacus/blob/master/.github/HELP.md')} /> + + + Face ID Lock + + + + + About + + + App Version + + {' '} + (BETA) + + + + Error report + Linking.openURL('https://github.com/victorbalssa/abacus/issues/new')} underline>New issue + + + Feature request + Linking.openURL('https://github.com/victorbalssa/abacus/discussions/new')} underline>New discussion + + + Sources + Linking.openURL('https://github.com/victorbalssa/abacus')} underline>GitHub.com + + + + Debug + + + Clear & Reset Application + setResetOpen(true)} /> + + + + + + + + + Are you sure? + + Clearing cache will remove: + <>- local configurations. + <>- Oauth Client ID & Secret. + <>- URL of your instance. + + + + + + + + + + + + ); +}; + +export default Configuration; diff --git a/src/native/components/Home.tsx b/src/components/Home.tsx similarity index 98% rename from src/native/components/Home.tsx rename to src/components/Home.tsx index 7e7df36..ec03cb4 100644 --- a/src/native/components/Home.tsx +++ b/src/components/Home.tsx @@ -7,8 +7,8 @@ import { VStack, ScrollView, } from 'native-base'; -import colors from '../../constants/colors'; -import { HomeDisplayType } from '../../models/firefly'; +import colors from '../constants/colors'; +import { HomeDisplayType } from '../models/firefly'; import RangeTitle from './UI/RangeTitle'; type DashboardType = { diff --git a/src/native/components/Oauth.tsx b/src/components/Oauth.tsx similarity index 84% rename from src/native/components/Oauth.tsx rename to src/components/Oauth.tsx index f90dce3..22f6e15 100644 --- a/src/native/components/Oauth.tsx +++ b/src/components/Oauth.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch } from 'react'; +import React from 'react'; import { Input, Box, @@ -8,19 +8,7 @@ import { import { KeyboardAvoidingView } from 'react-native'; import * as Haptics from 'expo-haptics'; -import { isValidHttpUrl } from '../../lib/common'; -import { OauthConfig } from '../../containers/Oauth'; - -type ConfigurationComponent = { - loading: boolean, - faceId: boolean, - faceIdCheck: () => Promise, - config: OauthConfig, - setConfig: Dispatch, - promptAsync: () => Promise, - backendURL: string, - setBackendURL: (state: string) => Promise, -} +import { isValidHttpUrl } from '../lib/common'; const Oauth = ({ loading, @@ -31,14 +19,14 @@ const Oauth = ({ promptAsync, backendURL, setBackendURL, -}: ConfigurationComponent) => ( +}) => ( - FireflyIII backend URL + Firefly III backend URL diff --git a/src/native/components/Transactions/Form.tsx b/src/components/Transactions/Form.tsx similarity index 100% rename from src/native/components/Transactions/Form.tsx rename to src/components/Transactions/Form.tsx diff --git a/src/native/components/Transactions/List.tsx b/src/components/Transactions/List.tsx similarity index 93% rename from src/native/components/Transactions/List.tsx rename to src/components/Transactions/List.tsx index e8b68fa..7b47802 100644 --- a/src/native/components/Transactions/List.tsx +++ b/src/components/Transactions/List.tsx @@ -1,29 +1,31 @@ import React from 'react'; import { RefreshControl } from 'react-native'; import { - Box, HStack, Icon, Pressable, Text, - ScrollView, VStack, Spacer, Skeleton, + Box, + HStack, + Icon, + Pressable, + Text, + VStack, + Skeleton, } from 'native-base'; import { - Entypo, Feather, MaterialCommunityIcons, MaterialIcons, + MaterialCommunityIcons, + MaterialIcons, } from '@expo/vector-icons'; import { SwipeListView } from 'react-native-swipe-list-view'; import moment from 'moment'; import RangeTitle from '../UI/RangeTitle'; -import colors from '../../../constants/colors'; - -type TransactionsType = { - loading: boolean, - transactions: [], - onRefresh: () => void, - onDeleteTransaction: (id: string) => Promise, - onEndReached: () => void, - onPressItem: (id: string, payload: {}) => void, -} +import colors from '../../constants/colors'; const Basic = ({ - loading, onRefresh, transactions, onDeleteTransaction, onEndReached, onPressItem, + loading, + onRefresh, + transactions, + onDeleteTransaction, + onEndReached, + onPressItem, }) => { const closeRow = (rowMap, rowKey) => { if (rowMap[rowKey]) { @@ -241,7 +243,7 @@ const Transactions = ({ onDeleteTransaction, onEndReached, onPressItem, -}: TransactionsType) => ( +}) => ( <> diff --git a/src/native/components/UI/Loading.tsx b/src/components/UI/Loading.tsx similarity index 100% rename from src/native/components/UI/Loading.tsx rename to src/components/UI/Loading.tsx diff --git a/src/components/UI/RangeTitle.tsx b/src/components/UI/RangeTitle.tsx new file mode 100644 index 0000000..3017338 --- /dev/null +++ b/src/components/UI/RangeTitle.tsx @@ -0,0 +1,84 @@ +import React, { FC } from 'react'; +import { + HStack, + Box, + Text, + Select, + CheckIcon, IconButton, +} from 'native-base'; +import { useDispatch, useSelector } from 'react-redux'; +import moment from 'moment'; +import { AntDesign } from '@expo/vector-icons'; +import * as Haptics from 'expo-haptics'; +import { RootDispatch, RootState } from '../../store'; + +const RangeTitle: FC = () => { + const firefly = useSelector((state: RootState) => state.firefly); + const dispatch = useDispatch(); + + return ( + + + dispatch.firefly.handleChangeRange({ direction: -1 })} + onTouchStart={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)} + /> + + + {firefly.rangeTitle} + + + {`${moment(firefly.start).format('ll')} - ${moment(firefly.end).format('ll')}`} + + + + + dispatch.firefly.handleChangeRange({ direction: 1 })} + onTouchStart={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)} + /> + + + ); +}; + +export default RangeTitle; diff --git a/src/native/components/UI/Title.tsx b/src/components/UI/Title.tsx similarity index 100% rename from src/native/components/UI/Title.tsx rename to src/components/UI/Title.tsx diff --git a/src/native/components/UI/ToastAlert.tsx b/src/components/UI/ToastAlert.tsx similarity index 100% rename from src/native/components/UI/ToastAlert.tsx rename to src/components/UI/ToastAlert.tsx diff --git a/src/containers/Chart.tsx b/src/containers/Chart.tsx index 3f3dc9d..67c618f 100644 --- a/src/containers/Chart.tsx +++ b/src/containers/Chart.tsx @@ -1,48 +1,22 @@ -import React from 'react'; -import { connect } from 'react-redux'; +import React, { FC } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { useToast } from 'native-base'; -import Layout from '../native/components/Chart'; -import { Dispatch, RootState } from '../store'; +import Layout from '../components/Chart'; +import { RootDispatch, RootState } from '../store'; -const mapStateToProps = (state: RootState) => ({ - range: state.firefly.range, - start: state.firefly.start, - end: state.firefly.end, - netWorth: state.firefly.netWorth, - spent: state.firefly.spent, - earned: state.firefly.earned, - balance: state.firefly.balance, - accounts: state.firefly.accounts, - loading: state.loading.models.firefly, - scrollEnabled: state.configuration.scrollEnabled, -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - filterData: dispatch.firefly.filterData, - handleChangeRange: dispatch.firefly.handleChangeRange, - getSummary: dispatch.firefly.getSummaryBasic, - getDashboard: dispatch.firefly.getDashboardBasic, - disableScroll: dispatch.configuration.disableScroll, - enableScroll: dispatch.configuration.enableScroll, -}); - -const Chart = ({ - loading, - start, - end, - accounts, - getSummary, - getDashboard, - filterData, - disableScroll, - enableScroll, - scrollEnabled, -}) => { +const Chart: FC = () => { const toast = useToast(); + const loading = useSelector((state: RootState) => state.loading.models.firefly); + const configuration = useSelector((state: RootState) => state.configuration); + const firefly = useSelector((state: RootState) => state.firefly); + const dispatch = useDispatch(); const fetchData = async () => { try { - await Promise.all([getSummary(), getDashboard()]); + await Promise.all([ + dispatch.firefly.getSummaryBasic(), + dispatch.firefly.getDashboardBasic(), + ]); } catch (e) { toast.show({ placement: 'top', @@ -55,16 +29,16 @@ const Chart = ({ return ( ); }; -export default connect(mapStateToProps, mapDispatchToProps)(Chart); +export default Chart; diff --git a/src/containers/Configuration.tsx b/src/containers/Configuration.tsx index e3c00ab..fd3e0f4 100644 --- a/src/containers/Configuration.tsx +++ b/src/containers/Configuration.tsx @@ -1,37 +1,17 @@ -import React from 'react'; -import { connect } from 'react-redux'; +import React, { FC } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { CommonActions } from '@react-navigation/native'; -import Layout from '../native/components/Configuration'; -import { Dispatch } from '../store'; -const mapStateToProps = (state) => ({ - backendURL: state.configuration.backendURL, - faceId: state.configuration.faceId, - loading: state.loading.models.configuration, -}); +import Layout from '../components/Configuration'; +import { RootDispatch, RootState } from '../store'; +import { ContainerPropType } from './types'; -const mapDispatchToProps = (dispatch: Dispatch) => ({ - resetAllStorage: dispatch.configuration.resetAllStorage, - setFaceId: dispatch.configuration.setFaceId, -}); +const ConfigurationContainer: FC = ({ navigation }: ContainerPropType) => { + const configuration = useSelector((state: RootState) => state.configuration); + const dispatch = useDispatch(); -interface ConfigurationContainerType extends ReturnType, - ReturnType { - navigation: { dispatch: (action) => void }, - loading: boolean, - backendURL: string, -} - -const ConfigurationContainer = ({ - loading, - navigation, - backendURL, - resetAllStorage, - faceId, - setFaceId, -}: ConfigurationContainerType) => { const resetApp = async () => { - await resetAllStorage(); + await dispatch.configuration.resetAllStorage(); navigation.dispatch( CommonActions.reset({ index: 0, @@ -44,13 +24,12 @@ const ConfigurationContainer = ({ return ( ); }; -export default connect(mapStateToProps, mapDispatchToProps)(ConfigurationContainer); +export default ConfigurationContainer; diff --git a/src/containers/Home.tsx b/src/containers/Home.tsx index b64d000..3804af3 100644 --- a/src/containers/Home.tsx +++ b/src/containers/Home.tsx @@ -1,54 +1,26 @@ -import React, { useEffect } from 'react'; -import { connect } from 'react-redux'; +import React, { useEffect, FC } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; import { useToast } from 'native-base'; -import Layout from '../native/components/Home'; -import { HomeDisplayType } from '../models/firefly'; +import Layout from '../components/Home'; +import { RootDispatch, RootState } from '../store'; -const mapStateToProps = (state) => ({ - loading: state.loading.models.firefly, - netWorth: state.firefly.netWorth, - spent: state.firefly.spent, - balance: state.firefly.balance, - earned: state.firefly.earned, -}); - -const mapDispatchToProps = (dispatch) => ({ - handleChangeRange: dispatch.firefly.handleChangeRange, - getSummary: dispatch.firefly.getSummaryBasic, - getDashboard: dispatch.firefly.getDashboardBasic, -}); - -type HomeContainerType = { - loading: boolean, - netWorth: HomeDisplayType[], - spent: HomeDisplayType[], - balance: HomeDisplayType[], - earned: HomeDisplayType[], - getSummary: () => Promise, - getDashboard: () => Promise, - handleChangeRange: (value?: object) => Promise, -}; - -const Home = ({ - loading, - netWorth, - spent, - balance, - earned, - getSummary, - getDashboard, - handleChangeRange, -}: HomeContainerType) => { +const Home: FC = () => { const toast = useToast(); + const loading = useSelector((state: RootState) => state.loading.models.firefly); + const firefly = useSelector((state: RootState) => state.firefly); + const dispatch = useDispatch(); useEffect(() => { - (async () => handleChangeRange())(); + dispatch.firefly.handleChangeRange({}); }, []); const fetchData = async () => { try { - await Promise.all([getSummary(), getDashboard()]); + await Promise.all([ + dispatch.firefly.getSummaryBasic(), + dispatch.firefly.getDashboardBasic(), + ]); } catch (e) { toast.show({ placement: 'top', @@ -61,13 +33,13 @@ const Home = ({ return ( ); }; -export default connect(mapStateToProps, mapDispatchToProps)(Home); +export default Home; diff --git a/src/containers/Oauth.tsx b/src/containers/Oauth.tsx index dd91d55..c7ef239 100644 --- a/src/containers/Oauth.tsx +++ b/src/containers/Oauth.tsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from 'react'; -import { connect } from 'react-redux'; +import React, { useState, useEffect, FC } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import * as SecureStore from 'expo-secure-store'; import axios from 'axios'; import { useAuthRequest, TokenResponse } from 'expo-auth-session'; @@ -7,50 +7,25 @@ import { CommonActions } from '@react-navigation/native'; import { useToast } from 'native-base'; import { Keyboard } from 'react-native'; import * as LocalAuthentication from 'expo-local-authentication'; -import Layout from '../native/components/Oauth'; + +import Layout from '../components/Oauth'; import secureKeys from '../constants/oauth'; import { discovery, redirectUri } from '../lib/oauth'; -import { RootState, Dispatch } from '../store'; - -const mapStateToProps = (state: RootState) => ({ - backendURL: state.configuration.backendURL, - faceId: state.configuration.faceId, - loading: state.loading.models.firefly, -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - setBackendURL: dispatch.configuration.setBackendURL, - testAccessToken: dispatch.firefly.testAccessToken, - getFreshAccessToken: dispatch.firefly.getFreshAccessToken, - getNewAccessToken: dispatch.firefly.getNewAccessToken, -}); - -interface OauthContainerType extends - ReturnType, - ReturnType { - navigation: { dispatch: (action) => void }, - loading: boolean, - backendURL: string, -} - -export type OauthConfig = { - oauthClientId: string, - oauthClientSecret: string, -} - -const OauthContainer = ({ - loading, - faceId, - navigation, - backendURL, - setBackendURL, - testAccessToken, - getNewAccessToken, - getFreshAccessToken, -}: OauthContainerType) => { +import { RootState, RootDispatch } from '../store'; +import { ContainerPropType, OauthConfigType } from './types'; + +const OauthContainer: FC = ({ navigation }: ContainerPropType) => { const toast = useToast(); + const loading = useSelector((state: RootState) => state.loading.models.firefly); + const configuration = useSelector((state: RootState) => state.configuration); + const dispatch = useDispatch(); + + const { + backendURL, + faceId, + } = configuration; - const [config, setConfig] = useState({ + const [config, setConfig] = useState({ oauthClientId: '', oauthClientSecret: '', }); @@ -93,12 +68,12 @@ const OauthContainer = ({ (async () => { const tokens = await SecureStore.getItemAsync(secureKeys.tokens); const storageValue = JSON.parse(tokens); - if (storageValue && storageValue.accessToken) { + if (storageValue && storageValue.accessToken && backendURL) { axios.defaults.headers.Authorization = `Bearer ${storageValue.accessToken}`; try { if (!TokenResponse.isTokenFresh(storageValue)) { - await getFreshAccessToken(storageValue.refreshToken); + await dispatch.firefly.getFreshAccessToken(storageValue.refreshToken); } await faceIdCheck(); @@ -134,13 +109,13 @@ const OauthContainer = ({ }; Keyboard.dismiss(); - await getNewAccessToken(payload); - await testAccessToken(); + await dispatch.firefly.getNewAccessToken(payload); + await dispatch.firefly.testAccessToken(); toast.show({ placement: 'top', title: 'Success', - description: 'Secure connexion ready with your FireflyIII instance.', + description: 'Secure connexion ready with your Firefly III instance.', }); await faceIdCheck(); } catch (e) { @@ -156,20 +131,16 @@ const OauthContainer = ({ return ( { - await promptAsync(); - }} - backendURL={backendURL} - setBackendURL={async (value) => { - await setBackendURL(value); - }} + promptAsync={promptAsync} + setBackendURL={dispatch.configuration.setBackendURL} /> ); }; -export default connect(mapStateToProps, mapDispatchToProps)(OauthContainer); +export default OauthContainer; diff --git a/src/containers/Transactions/Create.tsx b/src/containers/Transactions/Create.tsx index 34a2512..71784cb 100644 --- a/src/containers/Transactions/Create.tsx +++ b/src/containers/Transactions/Create.tsx @@ -1,66 +1,19 @@ -import React, { useEffect } from 'react'; -import { connect } from 'react-redux'; +import React, { FC } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { CommonActions } from '@react-navigation/native'; -import Layout from '../../native/components/Transactions/Create'; -import { Dispatch, RootState } from '../../store'; - -const mapStateToProps = (state: RootState) => ({ - loading: state.loading.models.transactions, - accounts: state.accounts.autocompleteAccounts, - loadingAutocomplete: state.loading.effects.accounts.getAutocompleteAccounts, -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - getTransactions: dispatch.transactions.getTransactions, - getAutocompleteAccounts: dispatch.accounts.getAutocompleteAccounts, - getBudgets: dispatch.budgets.getBudgets, - getCategories: dispatch.categories.getCategories, - getCurrencies: dispatch.currencies.getCurrencies, - createTransactions: dispatch.transactions.createTransactions, -}); - -interface CreateContainerType extends - ReturnType, - ReturnType { - loading: boolean, - navigation: { dispatch: (action) => void }, -} - -const Create = ({ - loading, - navigation, - accounts, - getTransactions, - getAutocompleteAccounts, - loadingAutocomplete, - getBudgets, - getCategories, - getCurrencies, - createTransactions, -}: CreateContainerType) => { - const fetchData = async () => { - try { - await Promise.all([ -/* getBudgets(), - getCategories(), - getCurrencies(),*/ - ]); - } catch (e) { - // catch 401 - } - }; - - const fetchTransactions = async () => { - try { - await getTransactions({ endReached: false }); - } catch (e) { - // catch 401 - } - }; - - const goToTransactions = async () => { - await fetchTransactions(); +import Layout from '../../components/Transactions/Create'; +import { RootDispatch, RootState } from '../../store'; +import { ContainerPropType } from '../types'; + +const Create: FC = ({ navigation }: ContainerPropType) => { + const loading = useSelector((state: RootState) => state.loading.models.transactions); + const accounts = useSelector((state: RootState) => state.accounts.autocompleteAccounts); + const loadingAutocomplete = useSelector((state: RootState) => state.loading.effects.accounts.getAutocompleteAccounts); + const dispatch = useDispatch(); + + const goToTransactions = () => { + dispatch.transactions.getTransactions({ endReached: false }); navigation.dispatch( CommonActions.navigate({ name: 'Transactions', @@ -68,21 +21,17 @@ const Create = ({ ); }; - useEffect(() => { - (async () => fetchData())(); - }, []); - return ( ); }; -export default connect(mapStateToProps, mapDispatchToProps)(Create); +export default Create; diff --git a/src/containers/Transactions/Edit.tsx b/src/containers/Transactions/Edit.tsx index efc3e0e..2c87de9 100644 --- a/src/containers/Transactions/Edit.tsx +++ b/src/containers/Transactions/Edit.tsx @@ -1,45 +1,27 @@ -import React from 'react'; -import { connect } from 'react-redux'; +import React, { FC } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { CommonActions } from '@react-navigation/native'; -import Layout from '../../native/components/Transactions/Edit'; -import { Dispatch, RootState } from '../../store'; +import Layout from '../../components/Transactions/Edit'; +import { RootDispatch, RootState } from '../../store'; +import { ContainerPropType } from '../types'; -const mapStateToProps = (state: RootState) => ({ - loading: state.loading.models.transactions, - budgets: state.budgets.budgets, - categories: state.categories.categories, - currencies: state.currencies.currencies, - accounts: state.accounts.autocompleteAccounts, - loadingAutocomplete: state.loading.effects.accounts.getAutocompleteAccounts, -}); +const Edit: FC = ({ navigation, route }: ContainerPropType) => { + const loading = useSelector((state: RootState) => state.loading.models.transactions); + const accounts = useSelector((state: RootState) => state.accounts.autocompleteAccounts); + const loadingAutocomplete = useSelector((state: RootState) => state.loading.effects.accounts.getAutocompleteAccounts); + const dispatch = useDispatch(); -const mapDispatchToProps = (dispatch: Dispatch) => ({ - getAutocompleteAccounts: dispatch.accounts.getAutocompleteAccounts, - updateTransactions: dispatch.transactions.updateTransactions, - getTransactions: dispatch.transactions.getTransactions, -}); - -const Edit = ({ - loading, - navigation, - route, - accounts, - getAutocompleteAccounts, - loadingAutocomplete, - updateTransactions, - getTransactions, -}) => { const { payload } = route.params; const onEdit = async (transaction) => { const { id } = route.params; - await updateTransactions({ id, transaction }); + await dispatch.transactions.updateTransactions({ id, transaction }); }; const fetchTransactions = async () => { try { - await getTransactions({ endReached: false }); + await dispatch.transactions.getTransactions({ endReached: false }); } catch (e) { // catch 401 } @@ -56,16 +38,16 @@ const Edit = ({ return ( ); }; -export default connect(mapStateToProps, mapDispatchToProps)(Edit); +export default Edit; diff --git a/src/containers/Transactions/List.tsx b/src/containers/Transactions/List.tsx index 197e2ed..cc29d51 100644 --- a/src/containers/Transactions/List.tsx +++ b/src/containers/Transactions/List.tsx @@ -1,28 +1,16 @@ -import React, { useEffect } from 'react'; -import { connect } from 'react-redux'; +import React, { FC, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { useToast } from 'native-base'; import { CommonActions } from '@react-navigation/native'; -import Layout from '../../native/components/Transactions/List'; -import { Dispatch, RootState } from '../../store'; +import Layout from '../../components/Transactions/List'; +import { RootDispatch, RootState } from '../../store'; +import { ContainerPropType } from '../types'; -const mapStateToProps = (state: RootState) => ({ - loading: state.loading.models.transactions, - transactions: state.transactions.transactions, -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - getTransactions: dispatch.transactions.getTransactions, - deleteTransaction: dispatch.transactions.deleteTransaction, -}); - -const List = ({ - loading, - navigation, - transactions, - getTransactions, - deleteTransaction, -}) => { +const List: FC = ({ navigation }: ContainerPropType) => { const toast = useToast(); + const loading = useSelector((state: RootState) => state.loading.models.transactions); + const transactions = useSelector((state: RootState) => state.transactions.transactions); + const dispatch = useDispatch(); // TODO: do not pass entire payload into this modal const goToEdit = (id, payload) => navigation.dispatch( @@ -37,7 +25,7 @@ const List = ({ const onRefresh = () => { try { - getTransactions({ endReached: false }); + dispatch.transactions.getTransactions({ endReached: false }); } catch (e) { console.error(e); toast.show({ @@ -50,7 +38,7 @@ const List = ({ const onDeleteTransaction = async (id) => { try { - await deleteTransaction({ id }); + await dispatch.transactions.deleteTransaction({ id }); } catch (e) { console.error(e); toast.show({ @@ -63,7 +51,7 @@ const List = ({ const onEndReached = () => { try { - getTransactions({ endReached: true }); + dispatch.transactions.getTransactions({ endReached: true }); } catch (e) { console.error(e); toast.show({ @@ -90,4 +78,4 @@ const List = ({ ); }; -export default connect(mapStateToProps, mapDispatchToProps)(List); +export default List; diff --git a/src/index.tsx b/src/index.tsx new file mode 100755 index 0000000..3d51d31 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,202 @@ +import React, { useEffect, useState, FC } from 'react'; +import { Provider } from 'react-redux'; +import { PersistGate } from 'redux-persist/es/integration/react'; +import { LogBox } from 'react-native'; +import { + AlertDialog, + Button, + extendTheme, + NativeBaseProvider, +} from 'native-base'; +import { StatusBar } from 'expo-status-bar'; +import AnimatedSplash from 'react-native-animated-splash-screen'; +import { LinearGradient } from 'expo-linear-gradient'; +import * as Updates from 'expo-updates'; +import { useFonts } from 'expo-font'; + +import { store, persistor } from './store'; +import colors from './constants/colors'; +import Routes from './routes'; +import Loading from './components/UI/Loading'; + +const config = { + dependencies: { + 'linear-gradient': LinearGradient, + }, +}; + +const theme = extendTheme({ + colors: { + primary: { + 50: colors.brandStyleSecond, + 100: colors.brandStyleSecond, + 200: colors.brandStyleSecond, + 300: colors.brandStyleSecond, + 400: colors.brandStyleSecond, + 500: colors.brandStyle, + 600: colors.brandStyle, + 700: colors.brandStyle, + 800: colors.brandStyle, + 900: colors.brandStyle, + }, + chart0: { + 600: colors.brandStyle0, + }, + chart1: { + 600: colors.brandStyle1, + }, + chart2: { + 600: colors.brandStyle2, + }, + chart3: { + 600: colors.brandStyle3, + }, + chart4: { + 600: colors.brandStyle4, + }, + }, + fontConfig: { + Montserrat: { + 100: { + normal: 'Montserrat_Light', + }, + 200: { + normal: 'Montserrat_Light', + }, + 300: { + normal: 'Montserrat', + }, + 400: { + normal: 'Montserrat', + }, + 500: { + normal: 'Montserrat_Bold', + }, + 600: { + normal: 'Montserrat_Bold', + }, + }, + }, + fonts: { + heading: 'Montserrat', + body: 'Montserrat', + mono: 'Montserrat', + }, + components: { + Alert: { + baseStyle: { + m: '3', + shadow: 2, + }, + }, + Heading: { + baseStyle: { + fontFamily: 'Montserrat_Bold', + }, + }, + IconButton: { + baseStyle: { + _icon: { + size: 'xl', + }, + _pressed: { + style: { + transform: [{ + scale: 0.95, + }], + opacity: 0.95, + }, + }, + }, + }, + Input: { + baseStyle: { + borderRadius: 15, + height: 10, + }, + }, + }, +}); + +const App: FC = () => { + LogBox.ignoreAllLogs(true); + + const OTARef = React.createRef(); + const [OTAOpen, setOTAOpen] = useState(false); + const [fontsLoaded] = useFonts({ + Montserrat: require('./fonts/Montserrat-Regular.ttf'), + Montserrat_Light: require('./fonts/Montserrat-Light.ttf'), + Montserrat_Bold: require('./fonts/Montserrat-Bold.ttf'), + }); + + const onOTAUpdate = async () => { + try { + await Updates.fetchUpdateAsync(); + await Updates.reloadAsync(); + } catch (e) { + console.error(e); + } + }; + + const onCheckOTA = async () => { + try { + const update = await Updates.checkForUpdateAsync(); + if (update.isAvailable) { + setOTAOpen(true); + } + } catch (e) { + console.error(e); + } + }; + + useEffect(() => { + (async () => { + await onCheckOTA(); + })(); + }, []); + + return ( + + + + + } + persistor={persistor} + > + {fontsLoaded && Routes} + + + + setOTAOpen(false)}> + + + New Update Available + + You can always update later in Settings tab. + + + + + + + + + + + + ); +}; + +export default App; diff --git a/src/models/firefly.ts b/src/models/firefly.ts index 6941cc2..ecf2eaa 100644 --- a/src/models/firefly.ts +++ b/src/models/firefly.ts @@ -120,7 +120,7 @@ export default createModel()({ * * @returns {Promise} */ - async handleChangeRange(payload, rootState) { + async handleChangeRange(payload = {}, rootState) { const { firefly: { start: oldStart, @@ -197,11 +197,14 @@ export default createModel()({ break; } + console.log('RANGE', range, rangeTitle); + console.log('DATE', start, end); + dispatch.firefly.setRange({ range, rangeTitle }); dispatch.firefly.setData({ start, end }); - dispatch.firefly.getSummaryBasic(0); - dispatch.firefly.getDashboardBasic(0); + dispatch.firefly.getSummaryBasic(); + dispatch.firefly.getDashboardBasic(); dispatch.transactions.getTransactions({ endReached: false }); }, @@ -210,7 +213,7 @@ export default createModel()({ * * @returns {Promise} */ - async getSummaryBasic(payload, rootState) { + async getSummaryBasic(_: void, rootState) { const { firefly: { start, @@ -251,7 +254,7 @@ export default createModel()({ * * @returns {Promise} */ - async getDashboardBasic(payload, rootState) { + async getDashboardBasic(_: void, rootState) { const { firefly: { start, @@ -280,8 +283,8 @@ export default createModel()({ y: value, }; }); - accounts[index].maxY = maxBy(accounts[index].entries, (o: { x: string, y: string}) => (o.y)).y; - accounts[index].minY = minBy(accounts[index].entries, (o: { x: string, y: string}) => (o.y)).y; + accounts[index].maxY = maxBy(accounts[index].entries, (o: { x: string, y: string }) => (o.y)).y; + accounts[index].minY = minBy(accounts[index].entries, (o: { x: string, y: string }) => (o.y)).y; }); this.setData({ accounts }); diff --git a/src/native/components/Configuration.tsx b/src/native/components/Configuration.tsx deleted file mode 100644 index 59ffbf0..0000000 --- a/src/native/components/Configuration.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React from 'react'; -import { - Box, - Stack, - Text, - HStack, - Heading, - Switch, AlertDialog, Button, -} from 'native-base'; -import * as Linking from 'expo-linking'; -import Constants from 'expo-constants'; -import { AntDesign } from '@expo/vector-icons'; - -type ConfigurationComponent = { - faceId: boolean, - setFaceId: () => Promise, - loading: boolean, - resetApp: () => Promise, - backendURL: string, -}; - -const Configuration = ({ - faceId, - setFaceId, - resetApp, - backendURL, -}: ConfigurationComponent) => { - const [isResetOpen, setResetOpen] = React.useState(false); - const onResetClose = () => setResetOpen(false); - const ResetRef = React.useRef(null); - - return ( - - Security - - - URL - Linking.openURL(backendURL)} underline>{backendURL} - - - Help - Linking.openURL('https://github.com/victorbalssa/abacus/blob/master/.github/HELP.md')} /> - - - Face ID Lock - - - - - About - - - App Version - - {Constants.manifest.version} - {' '} - (BETA) - - - - Error report - Linking.openURL('https://github.com/victorbalssa/abacus/issues/new')} underline>New issue - - - Feature request - Linking.openURL('https://github.com/victorbalssa/abacus/discussions/new')} underline>New discussion - - - Sources - Linking.openURL('https://github.com/victorbalssa/abacus')} underline>GitHub.com - - - - Debug - - - Clear & Reset Application - setResetOpen(true)} /> - - - - - - - Are you sure? - - Clearing cache will remove: - <>- local configurations. - <>- Oauth Client ID & Secret. - <>- URL of your instance. - - - - - - - - - - - ); -}; - -export default Configuration; diff --git a/src/native/components/UI/RangeTitle.tsx b/src/native/components/UI/RangeTitle.tsx deleted file mode 100644 index 78f395c..0000000 --- a/src/native/components/UI/RangeTitle.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; -import { - HStack, - Box, - Text, - Select, - CheckIcon, IconButton, -} from 'native-base'; -import { connect } from 'react-redux'; -import moment from 'moment'; -import { AntDesign } from '@expo/vector-icons'; -import * as Haptics from 'expo-haptics'; -import { Dispatch, RootState } from '../../../store'; - -const RangeTitle = ({ - start, - end, - range, - rangeTitle, - handleChangeRange, -}) => ( - - - handleChangeRange({ direction: -1 })} - onTouchStart={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)} - /> - - - {rangeTitle} - - - {`${moment(start).format('ll')} - ${moment(end).format('ll')}`} - - - - - handleChangeRange({ direction: 1 })} - onTouchStart={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)} - /> - - -); - -const mapStateToProps = (state: RootState) => ({ - start: state.firefly.start, - end: state.firefly.end, - range: state.firefly.range, - rangeTitle: state.firefly.rangeTitle, -}); - -const mapDispatchToProps = (state: Dispatch) => ({ - handleChangeRange: state.firefly.handleChangeRange, -}); - -export default connect(mapStateToProps, mapDispatchToProps)(RangeTitle); diff --git a/src/native/components/UI/UIButton.tsx b/src/native/components/UI/UIButton.tsx deleted file mode 100644 index 11e6429..0000000 --- a/src/native/components/UI/UIButton.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { ReactElement } from 'react'; - -import { TouchableOpacity } from 'react-native'; -import { Text } from 'native-base'; -import colors from '../../../constants/colors'; -import Loading from './Loading'; - -type UIButtonType = { - text: string, - style?: object, - onPress: () => Promise, - loading: boolean, - disabled?: boolean, - icon?: ReactElement, -}; - -const UIButton = ({ - text, - style, - onPress, - loading, - disabled, - icon, -}: UIButtonType) => ( - - {!loading && icon} - {!loading && ( - - {text} - - )} - {loading && } - -); - -UIButton.defaultProps = { - disabled: false, - icon: null, - style: null, -}; - -export default UIButton; diff --git a/src/native/index.tsx b/src/native/index.tsx deleted file mode 100755 index d06585e..0000000 --- a/src/native/index.tsx +++ /dev/null @@ -1,232 +0,0 @@ -import React from 'react'; -import * as Font from 'expo-font'; -import { Provider } from 'react-redux'; -import { PersistGate } from 'redux-persist/es/integration/react'; -import { LogBox } from 'react-native'; -import { - AlertDialog, - Button, - extendTheme, - NativeBaseProvider, -} from 'native-base'; -import { StatusBar } from 'expo-status-bar'; -import AnimatedSplash from 'react-native-animated-splash-screen'; -import { LinearGradient } from 'expo-linear-gradient'; -import * as Updates from 'expo-updates'; - -import { Store, Persistor } from '../store'; -import colors from '../constants/colors'; -import Routes from './routes/index'; -import Loading from './components/UI/Loading'; - -const config = { - dependencies: { - 'linear-gradient': LinearGradient, - }, -}; - -const theme = extendTheme({ - colors: { - primary: { - 50: colors.brandStyleSecond, - 100: colors.brandStyleSecond, - 200: colors.brandStyleSecond, - 300: colors.brandStyleSecond, - 400: colors.brandStyleSecond, - 500: colors.brandStyle, - 600: colors.brandStyle, - 700: colors.brandStyle, - 800: colors.brandStyle, - 900: colors.brandStyle, - }, - chart0: { - 600: colors.brandStyle0, - }, - chart1: { - 600: colors.brandStyle1, - }, - chart2: { - 600: colors.brandStyle2, - }, - chart3: { - 600: colors.brandStyle3, - }, - chart4: { - 600: colors.brandStyle4, - }, - }, - fontConfig: { - Montserrat: { - 100: { - normal: 'Montserrat_Light', - }, - 200: { - normal: 'Montserrat_Light', - }, - 300: { - normal: 'Montserrat', - }, - 400: { - normal: 'Montserrat', - }, - 500: { - normal: 'Montserrat_Bold', - }, - 600: { - normal: 'Montserrat_Bold', - }, - }, - }, - fonts: { - heading: 'Montserrat', - body: 'Montserrat', - mono: 'Montserrat', - }, - components: { - Alert: { - baseStyle: { - m: '3', - shadow: 2, - }, - }, - Heading: { - baseStyle: { - fontFamily: 'Montserrat_Bold', - }, - }, - IconButton: { - baseStyle: { - _icon: { - size: 'xl', - }, - _pressed: { - style: { - transform: [{ - scale: 0.95, - }], - opacity: 0.95, - }, - }, - }, - }, - Input: { - baseStyle: { - borderRadius: 15, - height: 10, - }, - }, - }, -}); - -type AppPropsType = { - store: Store, - persistor: Persistor, -}; - -type AppStateType = { - loading: boolean, - isOTAOpen: boolean, -}; - -export default class App extends React.Component { - constructor(props) { - super(props); - - this.state = { - loading: true, - isOTAOpen: false, - }; - } - - async componentDidMount() { - await this.loadAssets(); - await this.onCheckOTA(); - } - - async onOTAUpdate() { - try { - await Updates.fetchUpdateAsync(); - await Updates.reloadAsync(); - } catch (e) { - console.error(e); - } - } - - async onCheckOTA() { - try { - const update = await Updates.checkForUpdateAsync(); - if (update.isAvailable) { - this.setState({ isOTAOpen: true }); - } - } catch (e) { - console.error(e); - } - } - - async loadAssets() { - await Font.loadAsync({ - Montserrat: require('../fonts/Montserrat-Regular.ttf'), - Montserrat_Light: require('../fonts/Montserrat-Light.ttf'), - Montserrat_Bold: require('../fonts/Montserrat-Bold.ttf'), - }); - this.setState({ loading: false }); - } - - render() { - const { - store, - persistor, - } = this.props; - const { - loading, - isOTAOpen, - } = this.state; - - const OTARef = React.createRef(); - - LogBox.ignoreAllLogs(true); - - return ( - - - - - } - persistor={persistor} - > - {loading === false && Routes} - - - - this.setState({ isOTAOpen: false })}> - - - New Update Available - - You can always update later in Settings tab. - - - - - - - - - - - - ); - } -} diff --git a/src/native/routes/index.tsx b/src/routes/index.tsx similarity index 92% rename from src/native/routes/index.tsx rename to src/routes/index.tsx index b33c273..7c4e386 100644 --- a/src/native/routes/index.tsx +++ b/src/routes/index.tsx @@ -9,14 +9,14 @@ import { Svg, Path } from 'react-native-svg'; import { Box, IconButton } from 'native-base'; import { StyleSheet, Dimensions } from 'react-native'; -import OauthContainer from '../../containers/Oauth'; -import ConfigurationContainer from '../../containers/Configuration'; -import HomeContainer from '../../containers/Home'; -import ChartContainer from '../../containers/Chart'; -import TransactionsListContainer from '../../containers/Transactions/List'; -import TransactionsEditContainer from '../../containers/Transactions/Edit'; -import TransactionsCreateContainer from '../../containers/Transactions/Create'; -import colors from '../../constants/colors'; +import OauthContainer from '../containers/Oauth'; +import ConfigurationContainer from '../containers/Configuration'; +import HomeContainer from '../containers/Home'; +import ChartContainer from '../containers/Chart'; +import TransactionsListContainer from '../containers/Transactions/List'; +import TransactionsEditContainer from '../containers/Transactions/Edit'; +import TransactionsCreateContainer from '../containers/Transactions/Create'; +import colors from '../constants/colors'; const Stack = createNativeStackNavigator(); const Stack2 = createStackNavigator(); diff --git a/src/store/index.ts b/src/store/index.ts index 5702b31..ab65016 100755 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -22,5 +22,5 @@ export const store = init({ export const persistor = getPersistor(); export type Store = typeof store export type Persistor = typeof persistor -export type Dispatch = RematchDispatch +export type RootDispatch = RematchDispatch export type RootState = RematchRootState From e6780f1822638437c0046ad9b58791234b1ccd98 Mon Sep 17 00:00:00 2001 From: victorbalssa Date: Wed, 10 Aug 2022 14:40:30 -0700 Subject: [PATCH 2/8] [UPDATE] add types file. --- src/containers/types.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/containers/types.ts diff --git a/src/containers/types.ts b/src/containers/types.ts new file mode 100644 index 0000000..f78389d --- /dev/null +++ b/src/containers/types.ts @@ -0,0 +1,9 @@ +export type ContainerPropType = { + navigation: { dispatch: (action) => void }, + route: { params: { payload, id: string } }, +} + +export type OauthConfigType = { + oauthClientId: string, + oauthClientSecret: string, +} From 3c2a685aa3ed4a3329764d64b143299bbb986e31 Mon Sep 17 00:00:00 2001 From: victorbalssa Date: Thu, 11 Aug 2022 08:17:54 -0700 Subject: [PATCH 3/8] [UPDATE] Toast 3.0 :bread:. --- src/components/Home.tsx | 22 ++++------- src/components/UI/ErrorWidget.tsx | 30 +++++++++++++++ src/components/UI/RangeTitle.tsx | 3 ++ src/components/UI/ToastAlert.tsx | 5 +-- src/containers/Chart.tsx | 25 ++++++------- src/containers/Home.tsx | 24 +++--------- src/containers/Oauth.tsx | 51 +++++++++++++++++++------- src/containers/Transactions/Create.tsx | 6 +-- src/containers/Transactions/Edit.tsx | 5 ++- src/containers/Transactions/List.tsx | 22 ++--------- src/index.tsx | 1 + src/models/firefly.ts | 2 + src/models/transactions.ts | 5 +-- src/store/index.ts | 16 ++++---- 14 files changed, 117 insertions(+), 100 deletions(-) create mode 100644 src/components/UI/ErrorWidget.tsx diff --git a/src/components/Home.tsx b/src/components/Home.tsx index ec03cb4..ecf6588 100644 --- a/src/components/Home.tsx +++ b/src/components/Home.tsx @@ -1,33 +1,25 @@ import React from 'react'; -import { RefreshControl, StyleSheet, View } from 'react-native'; import { - Box, + RefreshControl, + View, +} from 'react-native'; +import { HStack, Text, VStack, ScrollView, } from 'native-base'; import colors from '../constants/colors'; -import { HomeDisplayType } from '../models/firefly'; import RangeTitle from './UI/RangeTitle'; -type DashboardType = { - loading: boolean, - netWorth: HomeDisplayType[], - spent: HomeDisplayType[], - earned: HomeDisplayType[], - balance: HomeDisplayType[], - fetchData: () => Promise, -} - const Home = ({ + loading, netWorth, spent, earned, balance, - loading, fetchData, -}: DashboardType) => ( +}) => ( <> - )} + )} > {netWorth.map((nw) => ( diff --git a/src/components/UI/ErrorWidget.tsx b/src/components/UI/ErrorWidget.tsx new file mode 100644 index 0000000..022acd4 --- /dev/null +++ b/src/components/UI/ErrorWidget.tsx @@ -0,0 +1,30 @@ +import React, { FC, useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { useToast } from 'native-base'; +import { RootState } from '../../store'; +import ToastAlert from './ToastAlert'; + +const ErrorWidget: FC = () => { + const toast = useToast(); + const { error } = useSelector((state: RootState) => state.loading.global); + + useEffect(() => { + if (error && (error as Error).message) { + toast.show({ + render: ({ id }) => ( + toast.close(id)} + title="Something went wrong" + status="error" + variant="solid" + description={(error as Error).message} + /> + ), + }); + } + }, [error]); + + return <>; +}; + +export default ErrorWidget; diff --git a/src/components/UI/RangeTitle.tsx b/src/components/UI/RangeTitle.tsx index 3017338..aaee057 100644 --- a/src/components/UI/RangeTitle.tsx +++ b/src/components/UI/RangeTitle.tsx @@ -11,6 +11,7 @@ import moment from 'moment'; import { AntDesign } from '@expo/vector-icons'; import * as Haptics from 'expo-haptics'; import { RootDispatch, RootState } from '../../store'; +import ErrorWidget from "./ErrorWidget"; const RangeTitle: FC = () => { const firefly = useSelector((state: RootState) => state.firefly); @@ -65,6 +66,8 @@ const RangeTitle: FC = () => { + + {}, }) => ( - + @@ -25,8 +25,7 @@ const ToastAlert = ({ } + icon={} _icon={{ color: 'lightText', }} diff --git a/src/containers/Chart.tsx b/src/containers/Chart.tsx index 67c618f..0a3bc7e 100644 --- a/src/containers/Chart.tsx +++ b/src/containers/Chart.tsx @@ -1,28 +1,25 @@ import React, { FC } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useToast } from 'native-base'; + import Layout from '../components/Chart'; import { RootDispatch, RootState } from '../store'; const Chart: FC = () => { - const toast = useToast(); - const loading = useSelector((state: RootState) => state.loading.models.firefly); + const { loading } = useSelector((state: RootState) => state.loading.models.firefly); const configuration = useSelector((state: RootState) => state.configuration); const firefly = useSelector((state: RootState) => state.firefly); const dispatch = useDispatch(); - const fetchData = async () => { + const fetchData = () => Promise.all([ + dispatch.firefly.getSummaryBasic(), + dispatch.firefly.getDashboardBasic(), + ]).catch(); + + const filterData = (payload) => { try { - await Promise.all([ - dispatch.firefly.getSummaryBasic(), - dispatch.firefly.getDashboardBasic(), - ]); + dispatch.firefly.filterData(payload); } catch (e) { - toast.show({ - placement: 'top', - title: 'Something went wrong', - description: e.message, - }); + console.error(e); } }; @@ -34,7 +31,7 @@ const Chart: FC = () => { accounts={firefly.accounts} scrollEnabled={configuration.scrollEnabled} fetchData={fetchData} - filterData={dispatch.firefly.filterData} + filterData={filterData} enableScroll={dispatch.configuration.enableScroll} disableScroll={dispatch.configuration.disableScroll} /> diff --git a/src/containers/Home.tsx b/src/containers/Home.tsx index 3804af3..93e06ef 100644 --- a/src/containers/Home.tsx +++ b/src/containers/Home.tsx @@ -1,34 +1,22 @@ import React, { useEffect, FC } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { useToast } from 'native-base'; import Layout from '../components/Home'; import { RootDispatch, RootState } from '../store'; const Home: FC = () => { - const toast = useToast(); - const loading = useSelector((state: RootState) => state.loading.models.firefly); + const { loading } = useSelector((state: RootState) => state.loading.models.firefly); const firefly = useSelector((state: RootState) => state.firefly); const dispatch = useDispatch(); useEffect(() => { - dispatch.firefly.handleChangeRange({}); + dispatch.firefly.handleChangeRange({}).catch(); }, []); - const fetchData = async () => { - try { - await Promise.all([ - dispatch.firefly.getSummaryBasic(), - dispatch.firefly.getDashboardBasic(), - ]); - } catch (e) { - toast.show({ - placement: 'top', - title: 'Something went wrong', - description: e.message, - }); - } - }; + const fetchData = () => Promise.all([ + dispatch.firefly.getSummaryBasic(), + dispatch.firefly.getDashboardBasic(), + ]).catch(); return ( { const toast = useToast(); - const loading = useSelector((state: RootState) => state.loading.models.firefly); + const { loading } = useSelector((state: RootState) => state.loading.models.firefly); const configuration = useSelector((state: RootState) => state.configuration); const dispatch = useDispatch(); @@ -79,9 +80,15 @@ const OauthContainer: FC = ({ navigation }: ContainerPropType) => { await faceIdCheck(); } catch (e) { toast.show({ - placement: 'top', - title: 'Error', - description: e.message, + render: ({ id }) => ( + toast.close(id)} + title="Something went wrong" + status="error" + variant="solid" + description={`Failed to get accessToken, ${e.message}`} + /> + ), }); } } @@ -92,9 +99,15 @@ const OauthContainer: FC = ({ navigation }: ContainerPropType) => { (async () => { if (result?.type === 'cancel') { toast.show({ - placement: 'top', - title: 'Info', - description: 'Authentication cancel, check Client ID & backend URL.', + render: ({ id }) => ( + toast.close(id)} + title="Info" + status="info" + variant="solid" + description="Authentication cancel, check Client ID & backend URL." + /> + ), }); } if (result?.type === 'success') { @@ -113,16 +126,28 @@ const OauthContainer: FC = ({ navigation }: ContainerPropType) => { await dispatch.firefly.testAccessToken(); toast.show({ - placement: 'top', - title: 'Success', - description: 'Secure connexion ready with your Firefly III instance.', + render: ({ id }) => ( + toast.close(id)} + title="Success" + status="success" + variant="solid" + description="Secure connexion ready with your Firefly III instance." + /> + ), }); await faceIdCheck(); } catch (e) { toast.show({ - placement: 'top', - title: 'Something went wrong', - description: `Failed to get accessToken, ${e.message}`, + render: ({ id }) => ( + toast.close(id)} + title="Something went wrong" + status="error" + variant="solid" + description={`Failed to get accessToken, ${e.message}`} + /> + ), }); } } diff --git a/src/containers/Transactions/Create.tsx b/src/containers/Transactions/Create.tsx index 71784cb..e9ca38f 100644 --- a/src/containers/Transactions/Create.tsx +++ b/src/containers/Transactions/Create.tsx @@ -1,15 +1,15 @@ import React, { FC } from 'react'; import { useDispatch, useSelector } from 'react-redux'; - import { CommonActions } from '@react-navigation/native'; + import Layout from '../../components/Transactions/Create'; import { RootDispatch, RootState } from '../../store'; import { ContainerPropType } from '../types'; const Create: FC = ({ navigation }: ContainerPropType) => { - const loading = useSelector((state: RootState) => state.loading.models.transactions); + const { loading } = useSelector((state: RootState) => state.loading.models.transactions); const accounts = useSelector((state: RootState) => state.accounts.autocompleteAccounts); - const loadingAutocomplete = useSelector((state: RootState) => state.loading.effects.accounts.getAutocompleteAccounts); + const { loading: loadingAutocomplete } = useSelector((state: RootState) => state.loading.effects.accounts.getAutocompleteAccounts); const dispatch = useDispatch(); const goToTransactions = () => { diff --git a/src/containers/Transactions/Edit.tsx b/src/containers/Transactions/Edit.tsx index 2c87de9..9caf336 100644 --- a/src/containers/Transactions/Edit.tsx +++ b/src/containers/Transactions/Edit.tsx @@ -1,14 +1,15 @@ import React, { FC } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { CommonActions } from '@react-navigation/native'; + import Layout from '../../components/Transactions/Edit'; import { RootDispatch, RootState } from '../../store'; import { ContainerPropType } from '../types'; const Edit: FC = ({ navigation, route }: ContainerPropType) => { - const loading = useSelector((state: RootState) => state.loading.models.transactions); + const { loading } = useSelector((state: RootState) => state.loading.models.transactions); const accounts = useSelector((state: RootState) => state.accounts.autocompleteAccounts); - const loadingAutocomplete = useSelector((state: RootState) => state.loading.effects.accounts.getAutocompleteAccounts); + const { loading: loadingAutocomplete } = useSelector((state: RootState) => state.loading.effects.accounts.getAutocompleteAccounts); const dispatch = useDispatch(); const { payload } = route.params; diff --git a/src/containers/Transactions/List.tsx b/src/containers/Transactions/List.tsx index cc29d51..70f4c3e 100644 --- a/src/containers/Transactions/List.tsx +++ b/src/containers/Transactions/List.tsx @@ -1,14 +1,13 @@ import React, { FC, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useToast } from 'native-base'; import { CommonActions } from '@react-navigation/native'; + import Layout from '../../components/Transactions/List'; import { RootDispatch, RootState } from '../../store'; import { ContainerPropType } from '../types'; const List: FC = ({ navigation }: ContainerPropType) => { - const toast = useToast(); - const loading = useSelector((state: RootState) => state.loading.models.transactions); + const { loading } = useSelector((state: RootState) => state.loading.models.transactions); const transactions = useSelector((state: RootState) => state.transactions.transactions); const dispatch = useDispatch(); @@ -28,24 +27,14 @@ const List: FC = ({ navigation }: ContainerPropType) => { dispatch.transactions.getTransactions({ endReached: false }); } catch (e) { console.error(e); - toast.show({ - placement: 'top', - title: 'Something went wrong', - description: e.message, - }); } }; const onDeleteTransaction = async (id) => { try { - await dispatch.transactions.deleteTransaction({ id }); + await dispatch.transactions.deleteTransaction(id); } catch (e) { console.error(e); - toast.show({ - placement: 'top', - title: 'Something went wrong', - description: e.message, - }); } }; @@ -54,11 +43,6 @@ const List: FC = ({ navigation }: ContainerPropType) => { dispatch.transactions.getTransactions({ endReached: true }); } catch (e) { console.error(e); - toast.show({ - placement: 'top', - title: 'Something went wrong', - description: e.message, - }); } }; diff --git a/src/index.tsx b/src/index.tsx index 3d51d31..22654aa 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -18,6 +18,7 @@ import { store, persistor } from './store'; import colors from './constants/colors'; import Routes from './routes'; import Loading from './components/UI/Loading'; +import ErrorWidget from "./components/UI/ErrorWidget"; const config = { dependencies: { diff --git a/src/models/firefly.ts b/src/models/firefly.ts index ecf2eaa..5771a47 100644 --- a/src/models/firefly.ts +++ b/src/models/firefly.ts @@ -97,6 +97,8 @@ export default createModel()({ }, filterData(state, payload) { + throw new Error('ARFF, fdsjkfljdskl'); + const { index: filterIndex } = payload; const { accounts } = state; const newAccounts = accounts.map((d) => (d)); diff --git a/src/models/transactions.ts b/src/models/transactions.ts index 4cb1969..68e7426 100644 --- a/src/models/transactions.ts +++ b/src/models/transactions.ts @@ -154,10 +154,7 @@ export default createModel()({ * * @returns {Promise} */ - async deleteTransaction(payload, rootState) { - const { - id, - } = payload; + async deleteTransaction(id, rootState) { const { transactions: { transactions, diff --git a/src/store/index.ts b/src/store/index.ts index ab65016..d93ea5e 100755 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,22 +1,20 @@ import { init, RematchDispatch, RematchRootState } from '@rematch/core'; -import createPersistPlugin, { getPersistor } from '@rematch/persist'; -import createLoadingPlugin, { ExtraModelsFromLoading } from '@rematch/loading'; +import persistPlugin, { getPersistor } from '@rematch/persist'; +import loadingPlugin, { ExtraModelsFromLoading } from '@rematch/loading'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { models, RootModel } from '../models'; -const persistPlugin = createPersistPlugin({ +const persistConfig = { key: 'root', storage: AsyncStorage, blacklist: [], -}); - -type FullModel = ExtraModelsFromLoading +}; -const loadingPlugin = createLoadingPlugin({}); +type FullModel = ExtraModelsFromLoading -export const store = init({ +export const store = init({ models, - plugins: [persistPlugin, loadingPlugin], + plugins: [persistPlugin(persistConfig), loadingPlugin({ type: 'full' })], }); export const persistor = getPersistor(); From 449dade39db443b8a45e02100735e957896de028 Mon Sep 17 00:00:00 2001 From: victorbalssa Date: Thu, 11 Aug 2022 09:06:03 -0700 Subject: [PATCH 4/8] [ADD] Autocomplete on description. --- src/components/Transactions/Create.tsx | 4 ++ src/components/Transactions/Edit.tsx | 2 + src/components/Transactions/Form.tsx | 55 +++++++++++++++++++++++--- src/containers/Transactions/Create.tsx | 3 ++ src/containers/Transactions/Edit.tsx | 1 + src/models/accounts.ts | 44 ++++++++++++++++++--- 6 files changed, 98 insertions(+), 11 deletions(-) diff --git a/src/components/Transactions/Create.tsx b/src/components/Transactions/Create.tsx index eca82c7..a228888 100644 --- a/src/components/Transactions/Create.tsx +++ b/src/components/Transactions/Create.tsx @@ -10,8 +10,10 @@ import TransactionForm from './Form'; const Create = ({ loading, accounts, + descriptions, submit, getAutocompleteAccounts, + getAutocompleteDescription, loadingAutocomplete, navigation, goToTransactions, @@ -29,7 +31,9 @@ const Create = ({ diff --git a/src/components/Transactions/Form.tsx b/src/components/Transactions/Form.tsx index de0a901..21dbb8e 100644 --- a/src/components/Transactions/Form.tsx +++ b/src/components/Transactions/Form.tsx @@ -35,7 +35,9 @@ const INITIAL_ERROR = { const Form = ({ accounts = [], loading, + descriptions = [], getAutocompleteAccounts, + getAutocompleteDescription, loadingAutocomplete, submit, goToTransactions, @@ -51,7 +53,7 @@ const Form = ({ }); const [errors, setErrors] = React.useState(INITIAL_ERROR); const [success, setSuccess] = React.useState(false); - const [displayAutocomplete, setDisplayAutocomplete] = React.useState({ source: false, destination: false }); + const [displayAutocomplete, setDisplayAutocomplete] = React.useState({ description: false, source: false, destination: false }); const resetErrors = () => setErrors(INITIAL_ERROR); @@ -147,10 +149,18 @@ const Form = ({ returnKeyType="done" placeholder="Description" value={formData.description} - onChangeText={(value) => setData({ - ...formData, - description: value, - })} + onChangeText={(value) => { + setData({ + ...formData, + description: value, + }); + getAutocompleteDescription({ query: value }); + }} + onFocus={() => { + getAutocompleteDescription({ query: formData.description }); + setDisplayAutocomplete({ description: true, source: false, destination: false }); + }} + onBlur={() => setDisplayAutocomplete({ description: false, source: false, destination: false })} InputRightElement={( {'description' in errors ? {errors.description} : <>} + + {displayAutocomplete.description && loadingAutocomplete && } + {displayAutocomplete.description && !loadingAutocomplete && ( + ( + { + setData({ + ...formData, + description: a.item.name, + }); + setDisplayAutocomplete({ description: false, source: false, destination: false }); + }} + _pressed={{ + borderRadius: 15, + backgroundColor: 'gray.300', + }} + > + + + {a.item.name || 'no name'} + + + + )} + /> + )} diff --git a/src/containers/Transactions/Create.tsx b/src/containers/Transactions/Create.tsx index e9ca38f..1d8dcf3 100644 --- a/src/containers/Transactions/Create.tsx +++ b/src/containers/Transactions/Create.tsx @@ -9,6 +9,7 @@ import { ContainerPropType } from '../types'; const Create: FC = ({ navigation }: ContainerPropType) => { const { loading } = useSelector((state: RootState) => state.loading.models.transactions); const accounts = useSelector((state: RootState) => state.accounts.autocompleteAccounts); + const descriptions = useSelector((state: RootState) => state.accounts.autocompleteDescriptions); const { loading: loadingAutocomplete } = useSelector((state: RootState) => state.loading.effects.accounts.getAutocompleteAccounts); const dispatch = useDispatch(); @@ -27,8 +28,10 @@ const Create: FC = ({ navigation }: ContainerPropType) => { loading={loading} loadingAutocomplete={loadingAutocomplete} accounts={accounts} + descriptions={descriptions} goToTransactions={goToTransactions} getAutocompleteAccounts={dispatch.accounts.getAutocompleteAccounts} + getAutocompleteDescription={dispatch.accounts.getAutocompleteDescriptions} submit={dispatch.transactions.createTransactions} /> ); diff --git a/src/containers/Transactions/Edit.tsx b/src/containers/Transactions/Edit.tsx index 9caf336..49f3198 100644 --- a/src/containers/Transactions/Edit.tsx +++ b/src/containers/Transactions/Edit.tsx @@ -46,6 +46,7 @@ const Edit: FC = ({ navigation, route }: ContainerPropType) => { accounts={accounts} goToTransactions={goToTransactions} getAutocompleteAccounts={dispatch.accounts.getAutocompleteAccounts} + getAutocompleteDescription={dispatch.accounts.getAutocompleteDescriptions} onEdit={onEdit} /> ); diff --git a/src/models/accounts.ts b/src/models/accounts.ts index 1b7945c..ce365ee 100644 --- a/src/models/accounts.ts +++ b/src/models/accounts.ts @@ -46,7 +46,7 @@ export type AccountType = { type: string, } -export type AutocompleteAccounts = { +export type AutocompleteAccount = { id: string, name: string, name_with_balance: string, @@ -58,18 +58,22 @@ export type AutocompleteAccounts = { currency_decimal_places: number, } -export type ApiAccountType = { - data: AccountType[], +export type AutocompleteDescription = { + id: string, + name: string, + description: string, } export type AccountStateType = { accounts: AccountType[], - autocompleteAccounts: AutocompleteAccounts[], + autocompleteAccounts: AutocompleteAccount[], + autocompleteDescriptions: AutocompleteDescription[], } const INITIAL_STATE = { accounts: [], autocompleteAccounts: [], + autocompleteDescriptions: [], } as AccountStateType; export default createModel()({ @@ -99,6 +103,17 @@ export default createModel()({ }; }, + setAutocompleteDescriptions(state, payload): AccountStateType { + const { + autocompleteDescriptions = state.autocompleteDescriptions, + } = payload; + + return { + ...state, + autocompleteDescriptions, + }; + }, + resetState() { return INITIAL_STATE; }, @@ -132,9 +147,26 @@ export default createModel()({ { url: `/api/v1/autocomplete/accounts?types=Asset%20account,${type},Loan,Debt,Mortgage&limit=${limit}&query=${query}` }, ); - console.log('autocompleteAccounts', autocompleteAccounts); - dispatch.accounts.setAutocompleteAccounts({ autocompleteAccounts }); }, + + /** + * Get autocomplete accounts with query + * + * @returns {Promise} + */ + async getAutocompleteDescriptions(payload): Promise { + const limit = 5; + const { + query, + } = payload; + const autocompleteDescriptions = await dispatch.configuration.apiFetch( + { url: `/api/v1/autocomplete/transactions?limit=${limit}&query=${query}` }, + ); + + console.log('autocompleteDescriptions', autocompleteDescriptions); + + dispatch.accounts.setAutocompleteDescriptions({ autocompleteDescriptions }); + }, }), }); From 29a257a4608f06392c86c2e21ebecbb0fa77c2c1 Mon Sep 17 00:00:00 2001 From: victorbalssa Date: Thu, 11 Aug 2022 09:08:18 -0700 Subject: [PATCH 5/8] [ADD] Autocomplete on description edit. --- src/components/Transactions/Edit.tsx | 2 ++ src/containers/Transactions/Create.tsx | 2 +- src/containers/Transactions/Edit.tsx | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Transactions/Edit.tsx b/src/components/Transactions/Edit.tsx index b5be072..9c594e8 100644 --- a/src/components/Transactions/Edit.tsx +++ b/src/components/Transactions/Edit.tsx @@ -11,6 +11,7 @@ const Edit = ({ loading, payload, accounts, + descriptions, onEdit, getAutocompleteAccounts, getAutocompleteDescription, @@ -32,6 +33,7 @@ const Edit = ({ payload={payload} loading={loading} accounts={accounts} + descriptions={descriptions} goToTransactions={goToTransactions} getAutocompleteAccounts={getAutocompleteAccounts} getAutocompleteDescription={getAutocompleteDescription} diff --git a/src/containers/Transactions/Create.tsx b/src/containers/Transactions/Create.tsx index 1d8dcf3..155e04a 100644 --- a/src/containers/Transactions/Create.tsx +++ b/src/containers/Transactions/Create.tsx @@ -10,7 +10,7 @@ const Create: FC = ({ navigation }: ContainerPropType) => { const { loading } = useSelector((state: RootState) => state.loading.models.transactions); const accounts = useSelector((state: RootState) => state.accounts.autocompleteAccounts); const descriptions = useSelector((state: RootState) => state.accounts.autocompleteDescriptions); - const { loading: loadingAutocomplete } = useSelector((state: RootState) => state.loading.effects.accounts.getAutocompleteAccounts); + const { loading: loadingAutocomplete } = useSelector((state: RootState) => state.loading.models.accounts); const dispatch = useDispatch(); const goToTransactions = () => { diff --git a/src/containers/Transactions/Edit.tsx b/src/containers/Transactions/Edit.tsx index 49f3198..b2c5f93 100644 --- a/src/containers/Transactions/Edit.tsx +++ b/src/containers/Transactions/Edit.tsx @@ -9,7 +9,8 @@ import { ContainerPropType } from '../types'; const Edit: FC = ({ navigation, route }: ContainerPropType) => { const { loading } = useSelector((state: RootState) => state.loading.models.transactions); const accounts = useSelector((state: RootState) => state.accounts.autocompleteAccounts); - const { loading: loadingAutocomplete } = useSelector((state: RootState) => state.loading.effects.accounts.getAutocompleteAccounts); + const descriptions = useSelector((state: RootState) => state.accounts.autocompleteDescriptions); + const { loading: loadingAutocomplete } = useSelector((state: RootState) => state.loading.models.accounts); const dispatch = useDispatch(); const { payload } = route.params; @@ -44,6 +45,7 @@ const Edit: FC = ({ navigation, route }: ContainerPropType) => { loadingAutocomplete={loadingAutocomplete} payload={payload} accounts={accounts} + descriptions={descriptions} goToTransactions={goToTransactions} getAutocompleteAccounts={dispatch.accounts.getAutocompleteAccounts} getAutocompleteDescription={dispatch.accounts.getAutocompleteDescriptions} From fd04f965f13979b9c0fb7a089e5efaf81d8ad91f Mon Sep 17 00:00:00 2001 From: victorbalssa Date: Thu, 11 Aug 2022 09:44:07 -0700 Subject: [PATCH 6/8] [ADD] modal delete transactions. --- src/components/Transactions/Form.tsx | 12 ++++---- src/components/Transactions/List.tsx | 44 ++++++++++++++++++++++++---- src/containers/Transactions/List.tsx | 2 ++ 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/components/Transactions/Form.tsx b/src/components/Transactions/Form.tsx index 21dbb8e..2cd5b19 100644 --- a/src/components/Transactions/Form.tsx +++ b/src/components/Transactions/Form.tsx @@ -233,9 +233,9 @@ const Form = ({ }} onFocus={() => { getAutocompleteAccounts({ query: formData.source_name, isDestination: false }); - setDisplayAutocomplete({ source: true, destination: false }); + setDisplayAutocomplete({ description: false, source: true, destination: false }); }} - onBlur={() => setDisplayAutocomplete({ source: false, destination: false })} + onBlur={() => setDisplayAutocomplete({ description: false, source: false, destination: false })} InputRightElement={( { getAutocompleteAccounts({ query: formData.destination_name, isDestination: true }); - setDisplayAutocomplete({ source: false, destination: true }); + setDisplayAutocomplete({ description: false, source: false, destination: true }); }} - onBlur={() => setDisplayAutocomplete({ source: false, destination: false })} + onBlur={() => setDisplayAutocomplete({ description: false, source: false, destination: false })} InputRightElement={( { + const [deleteModal, setDeleteModal] = React.useState({ open: false, item: {}, map: {} }); + const DeleteModalRef = React.useRef(null); + const closeRow = (rowMap, rowKey) => { if (rowMap[rowKey]) { rowMap[rowKey].closeRow(); } }; - const deleteRow = async (rowMap, rowKey) => { - closeRow(rowMap, rowKey); - await onDeleteTransaction(rowKey); + const onDeleteModalClose = () => { + closeRow(deleteModal.map, deleteModal.item.id); + setDeleteModal({ open: false, item: {}, map: {} }); + }; + + const deleteRow = async () => { + await onDeleteTransaction(deleteModal.item.id); + setDeleteModal({ open: false, item: {}, map: {} }); }; const colorItemTypes = { @@ -146,7 +155,7 @@ const Basic = ({ bg="red.500" justifyContent="center" borderRightRadius={15} - onPress={() => deleteRow(rowMap, data.item.id)} + onPress={() => setDeleteModal({ open: true, item: data.item, map: rowMap })} _pressed={{ opacity: 0.5, }} @@ -230,14 +239,36 @@ const Basic = ({ )} + + + + Are you sure? + + Transaction will be permanently removed: + {deleteModal.item?.attributes?.transactions[0].description} + {`Id: ${deleteModal.item?.id}`} + + + + + + + + + -)} + )} /> ); }; const Transactions = ({ loading, + loadingDelete, transactions, onRefresh, onDeleteTransaction, @@ -249,6 +280,7 @@ const Transactions = ({ { const { loading } = useSelector((state: RootState) => state.loading.models.transactions); + const { loading: loadingDelete } = useSelector((state: RootState) => state.loading.effects.transactions.deleteTransaction); const transactions = useSelector((state: RootState) => state.transactions.transactions); const dispatch = useDispatch(); @@ -53,6 +54,7 @@ const List: FC = ({ navigation }: ContainerPropType) => { return ( Date: Thu, 11 Aug 2022 09:47:02 -0700 Subject: [PATCH 7/8] 0.2.2 --- app.json | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app.json b/app.json index 58e8d00..8540784 100644 --- a/app.json +++ b/app.json @@ -7,7 +7,7 @@ "platforms": [ "ios" ], - "version": "0.2.1", + "version": "0.2.2", "orientation": "portrait", "icon": "./src/images/icon-abacus.png", "splash": { @@ -20,7 +20,7 @@ "url": "https://u.expo.dev/292ed6dc-804c-4444-95f5-fa5d76d9913b" }, "ios": { - "buildNumber": "0.2.1", + "buildNumber": "0.2.2", "bundleIdentifier": "abacus.ios.app", "infoPlist": { "NSFaceIDUsageDescription": "Abacus use Authentication with TouchId or FaceID" diff --git a/package-lock.json b/package-lock.json index d4086f0..c10bed3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "abacus", - "version": "0.2.1", + "version": "0.2.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "abacus", - "version": "0.2.1", + "version": "0.2.2", "dependencies": { "@expo/vector-icons": "^13.0.0", "@react-native-async-storage/async-storage": "~1.17.3", diff --git a/package.json b/package.json index ed7637f..67dfdce 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "abacus", "homepage": "https://github.com/victorbalssa/abacus", - "version": "0.2.1", + "version": "0.2.2", "private": true, "jest": { "preset": "jest-expo", From 52242eac184f8d42a4e55cd2fb67dd763499d347 Mon Sep 17 00:00:00 2001 From: victorbalssa Date: Thu, 11 Aug 2022 10:14:28 -0700 Subject: [PATCH 8/8] [FIX] #28 --- src/models/transactions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/models/transactions.ts b/src/models/transactions.ts index 68e7426..fe4507f 100644 --- a/src/models/transactions.ts +++ b/src/models/transactions.ts @@ -105,6 +105,7 @@ export default createModel()({ /* currency_id: '12', */ /* foreign_amount: '123.45', */ /* foreign_currency_id: '17', */ + amount: payload.amount.replace(/,/g, '.'), ...payload, }],