├── src ├── App.css ├── logo.png ├── assets │ ├── user.png │ └── logo-sidebar.png ├── app │ ├── layouts │ │ ├── LandingLayout.jsx │ │ └── DashboardLayout.jsx │ ├── errors │ │ └── 404.jsx │ ├── common │ │ ├── ToolsSidebar.jsx │ │ ├── CurrencySidebar.jsx │ │ └── calculator │ │ │ ├── calculator.css │ │ │ └── Calculator.js │ ├── dashboard │ │ ├── AppInlineProfile.jsx │ │ ├── AppMenu.jsx │ │ ├── AppFooter.jsx │ │ ├── ScrollToTop.jsx │ │ ├── AppTopbar.jsx │ │ ├── AppSubmenu.jsx │ │ └── Dashboard.jsx │ ├── locale │ │ ├── i18n.js │ │ ├── translations │ │ │ ├── bn.js │ │ │ └── en.js │ │ └── LocaleToggle.jsx │ ├── landing │ │ └── Website.jsx │ ├── income │ │ ├── IncomeListItem.jsx │ │ ├── EditIncomeCategory.jsx │ │ ├── EditIncome.jsx │ │ ├── IncomeCategory.jsx │ │ └── Income.jsx │ ├── expense │ │ ├── ExpenseListItem.jsx │ │ ├── EditExpenseCategory.jsx │ │ ├── EditExpense.jsx │ │ └── ExpenseCategory.jsx │ ├── auth │ │ ├── Login.jsx │ │ └── Register.jsx │ ├── analytics │ │ └── Analytics.jsx │ ├── profile │ │ ├── Profile.jsx │ │ └── EditProfile.jsx │ ├── setting │ │ └── Setting.jsx │ └── calendar │ │ └── TransactionCalendar.jsx ├── setupTests.js ├── App.test.js ├── Store.js ├── index.js ├── Helpers.js ├── App.jsx ├── API.js ├── Routes.jsx ├── Axios.js ├── serviceWorker.js └── extra │ └── blueberry-orange.css ├── .env.example ├── public ├── robots.txt ├── favicon.ico ├── assets │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ └── android-chrome-512x512.png ├── manifest.json └── index.html ├── screenshots ├── screen_1.png ├── screen_2.png ├── screen_3.png ├── screen_4.png ├── screen_5.png ├── screen_6.png └── screen_7.png ├── .gitignore ├── LICENSE ├── package.json └── README.md /src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/src/logo.png -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_API_HOST=http://localhost:8000 2 | REACT_APP_APP_NAME="Expense Tracker" 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/src/assets/user.png -------------------------------------------------------------------------------- /screenshots/screen_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/screenshots/screen_1.png -------------------------------------------------------------------------------- /screenshots/screen_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/screenshots/screen_2.png -------------------------------------------------------------------------------- /screenshots/screen_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/screenshots/screen_3.png -------------------------------------------------------------------------------- /screenshots/screen_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/screenshots/screen_4.png -------------------------------------------------------------------------------- /screenshots/screen_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/screenshots/screen_5.png -------------------------------------------------------------------------------- /screenshots/screen_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/screenshots/screen_6.png -------------------------------------------------------------------------------- /screenshots/screen_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/screenshots/screen_7.png -------------------------------------------------------------------------------- /src/assets/logo-sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/src/assets/logo-sidebar.png -------------------------------------------------------------------------------- /public/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/public/assets/favicon-32x32.png -------------------------------------------------------------------------------- /public/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/public/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /public/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/public/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/assets/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rahulhaque/expense-tracker-react/HEAD/public/assets/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/app/layouts/LandingLayout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const LandingLayout = (props) => { 4 | return ( 5 |
6 | {props.children} 7 |
8 | ); 9 | } 10 | 11 | export default LandingLayout; 12 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # IDE 27 | .vscode 28 | .idea 29 | -------------------------------------------------------------------------------- /src/app/errors/404.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const PageNotFound = (props) => { 4 | 5 | return ( 6 |
7 |
8 |
404
9 |
10 |

Make sure you know where you're going.

11 |
12 | ) 13 | 14 | } 15 | 16 | export default React.memo(PageNotFound); 17 | -------------------------------------------------------------------------------- /src/app/common/ToolsSidebar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Sidebar } from 'primereact/sidebar'; 3 | import Calculator from "./calculator/Calculator"; 4 | 5 | const ToolsSidebar = (props) => { 6 | return ( 7 | 8 |

Tools

9 | {props.visible && } 10 |
11 | ); 12 | } 13 | 14 | export default React.memo(ToolsSidebar); 15 | -------------------------------------------------------------------------------- /src/app/dashboard/AppInlineProfile.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useTracked } from './../../Store'; 3 | 4 | const AppInlineProfile = (props) => { 5 | 6 | const [state] = useTracked(); 7 | 8 | return ( 9 |
10 |
11 | logo 12 |
13 | e.preventDefault()}> 14 | {state.user.name} 15 | 16 |
17 | ); 18 | } 19 | 20 | export default React.memo(AppInlineProfile); 21 | -------------------------------------------------------------------------------- /src/app/dashboard/AppMenu.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import AppSubmenu from './AppSubmenu'; 5 | 6 | const AppMenu = (props) => { 7 | return ( 8 |
9 | 10 |
11 | ) 12 | } 13 | 14 | AppMenu.defaultProps = { 15 | model: null, 16 | onMenuItemClick: null 17 | }; 18 | 19 | AppMenu.propTypes = { 20 | model: PropTypes.array, 21 | onMenuItemClick: PropTypes.func 22 | }; 23 | 24 | export default React.memo(AppMenu); 25 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Expense Tracker", 3 | "name": "Expense Tracker", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "./assets/android-chrome-192x192.png", 12 | "sizes": "192x192", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "./assets/android-chrome-512x512.png", 17 | "sizes": "512x512", 18 | "type": "image/png" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#edf0f5", 24 | "background_color": "#edf0f5" 25 | } 26 | -------------------------------------------------------------------------------- /src/app/locale/i18n.js: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { initReactI18next } from 'react-i18next'; 3 | 4 | import { getItem } from './../../Helpers'; 5 | 6 | import bn from './translations/bn'; 7 | import en from './translations/en'; 8 | 9 | i18n 10 | .use(initReactI18next) // passes i18n down to react-i18next 11 | .init({ 12 | resources: { 13 | en: en, 14 | bn: bn 15 | }, 16 | lng: getItem('language') ? getItem('language') : 'en', 17 | keySeparator: false, // we do not use keys in form messages.welcome 18 | interpolation: { 19 | escapeValue: false // react already safes from xss 20 | } 21 | }); 22 | 23 | export default i18n; 24 | -------------------------------------------------------------------------------- /src/Store.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { createContainer } from 'react-tracked'; 3 | 4 | import { loadState, saveState } from './Helpers'; 5 | 6 | const globalState = { 7 | // Declare your global variable and functions here 8 | layoutMode: 'static', 9 | layoutColorMode: 'dark', 10 | currencies: [], 11 | currentCurrency: null, 12 | user: null 13 | }; 14 | 15 | const useLocalState = () => { 16 | const [processedState, setProcessedState] = useState((loadState() || globalState)); 17 | useEffect(() => { 18 | saveState(processedState); 19 | }, [processedState]); 20 | return [processedState, setProcessedState]; 21 | }; 22 | 23 | export const { Provider, useTracked } = createContainer(useLocalState); 24 | -------------------------------------------------------------------------------- /src/app/dashboard/AppFooter.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as dayjs from 'dayjs'; 3 | import packageJson from './../../../package.json'; 4 | 5 | const AppFooter = (props) => { 6 | return ( 7 |
8 |
9 | {process.env.REACT_APP_APP_NAME} 10 | 11 | Copyright © {dayjs().format('YYYY')} 12 |
13 | ver.{packageJson.version} 14 |
15 | ); 16 | } 17 | 18 | export default React.memo(AppFooter); 19 | -------------------------------------------------------------------------------- /src/app/landing/Website.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom'; 3 | 4 | import LandingLayout from './../layouts/LandingLayout'; 5 | 6 | const Website = (props) => { 7 | return ( 8 | 9 |
10 | 11 |
12 |

Expense

13 |

Manager

14 |

15 | Login | Register 16 |

17 |
18 |
19 |
20 | ) 21 | } 22 | 23 | export default Website; 24 | -------------------------------------------------------------------------------- /src/app/income/IncomeListItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card } from 'primereact/card'; 3 | import * as dayjs from 'dayjs'; 4 | 5 | const IncomeListItem = (props) => { 6 | const itemDetail = props.itemDetail; 7 | return ( 8 | 9 |
10 |
{itemDetail.source}
{itemDetail.amount.toLocaleString()} {itemDetail.currency_code}.
11 |
{itemDetail.category_name}
12 |
{dayjs(itemDetail.transaction_date).format('YYYY-MM-DD hh:mma')}
13 |
14 |
15 | ); 16 | } 17 | 18 | export default React.memo(IncomeListItem); 19 | -------------------------------------------------------------------------------- /src/app/dashboard/ScrollToTop.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from 'react'; 2 | 3 | 4 | const ScrollToTop = () => { 5 | 6 | const [showScroll, setShowScroll] = useState(false) 7 | 8 | const checkScrollTop = useCallback(() => { 9 | if (!showScroll && window.pageYOffset > 100) { 10 | setShowScroll(true) 11 | } else if (showScroll && window.pageYOffset <= 100) { 12 | setShowScroll(false) 13 | } 14 | }, [showScroll]); 15 | 16 | const scrollToTop = () => { 17 | window.scrollTo({ top: 0, behavior: 'smooth' }); 18 | }; 19 | 20 | window.addEventListener('scroll', checkScrollTop) 21 | 22 | return ( 23 | 24 | ); 25 | } 26 | 27 | export default React.memo(ScrollToTop); 28 | -------------------------------------------------------------------------------- /src/app/locale/translations/bn.js: -------------------------------------------------------------------------------- 1 | export default { 2 | translation: { 3 | lng: 'বাংলা', 4 | name: 'নাম', 5 | email: 'ইমেইল', 6 | mobile: 'ফোন', 7 | password: 'পাসওয়ার্ড', 8 | confirm_password: 'কনফার্ম পাসওয়ার্ড', 9 | yes: 'হ্যা', 10 | no: 'না', 11 | login: 'লগইন', 12 | sign_in: 'সাইন ইন', 13 | enter_login_credential: 'আপনার ইমেইল এবং পাসওয়ার্ড দিন', 14 | register: 'রেজিস্টার', 15 | enter_registration_credential: 'রেজিস্ট্রেশান এর জন্য তথ্য দিন', 16 | forgot_pass: 'পাসওয়ার্ড উদ্ধার', 17 | back_to_login: 'লগইন এ ফিরে যান', 18 | enter_pin: 'মেসেজ এ প্রাপ্ত পিন প্রবেশ করুন।', 19 | please: 'অনুগ্রহপূর্বক', 20 | verify: 'যাচাই', 21 | send: 'পাঠান', 22 | again: 'আবার', 23 | send_again: 'আবার পাঠান', 24 | no_code_received: 'কোন পিন পাননি?', 25 | expense: 'খরচ', 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/expense/ExpenseListItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card } from 'primereact/card'; 3 | import * as dayjs from 'dayjs'; 4 | 5 | const ExpenseListItem = (props) => { 6 | const itemDetail = props.itemDetail; 7 | 8 | return ( 9 | 10 |
11 |
{itemDetail.spent_on}
{itemDetail.amount.toLocaleString()} {itemDetail.currency_code}.
12 |
{itemDetail.category_name}
13 |
{dayjs(itemDetail.transaction_date).format('YYYY-MM-DD hh:mma')}
14 |
15 |
16 | ); 17 | } 18 | 19 | export default React.memo(ExpenseListItem); 20 | -------------------------------------------------------------------------------- /src/app/locale/translations/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | translation: { 3 | lng: 'English', 4 | name: 'Name', 5 | email: 'Email', 6 | mobile: 'Mobile', 7 | password: 'Password', 8 | confirm_password: 'Confirm Password', 9 | are_you_truck_owner: 'Are you a truck owner?', 10 | yes: 'Yes', 11 | no: 'No', 12 | login: 'Login', 13 | sign_in: 'Sign-in', 14 | enter_login_credential: 'Enter your credentials to login', 15 | register: 'Register', 16 | enter_registration_credential: 'Enter your info to register', 17 | forgot_pass: 'Forgot password?', 18 | back_to_login: 'Back to login', 19 | enter_pin: 'Enter the pin you received.', 20 | please: 'Please', 21 | verify: 'Verify', 22 | send: 'Send', 23 | again: 'Again', 24 | send_again: 'Send Again', 25 | no_code_received: 'Did\'t receive any code?', 26 | expense: 'Expense', 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/locale/LocaleToggle.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { useTranslation } from 'react-i18next'; 4 | 5 | import { Button } from 'primereact/button'; 6 | 7 | import { setItem } from './../../Helpers'; 8 | 9 | const LocaleToggle = (props) => { 10 | 11 | const [t, i18n] = useTranslation(); 12 | 13 | const toggleLanguage = useCallback(() => { 14 | i18n.language === 'en' ? i18n.changeLanguage('bn') : i18n.changeLanguage('en') 15 | setItem('language', i18n.language); 16 | }, [i18n]); 17 | 18 | return ( 19 |