├── src ├── components │ ├── Header.vue │ ├── Balance.vue │ ├── IncomeExpenses.vue │ ├── TransactionList.vue │ └── AddTransaction.vue ├── main.js ├── App.vue └── assets │ └── style.css ├── public ├── screen.png └── favicon.ico ├── vite.config.js ├── index.html ├── package.json ├── .gitignore └── README.md /src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradtraversy/vue-expense-tracker/HEAD/public/screen.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradtraversy/vue-expense-tracker/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import Toast from 'vue-toastification'; 3 | import 'vue-toastification/dist/index.css'; 4 | import './assets/style.css'; 5 | import App from './App.vue'; 6 | 7 | const app = createApp(App); 8 | app.use(Toast); 9 | app.mount('#app'); 10 | -------------------------------------------------------------------------------- /src/components/Balance.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | vue(), 10 | ], 11 | resolve: { 12 | alias: { 13 | '@': fileURLToPath(new URL('./src', import.meta.url)) 14 | } 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Expense Tracker 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-expense-tracker", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "vue": "^3.3.4", 12 | "vue-toastification": "^2.0.0-rc.5" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-vue": "^4.4.0", 16 | "vite": "^4.4.11" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode 22 | .vscode/* 23 | !.vscode/extensions.json 24 | .idea 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | -------------------------------------------------------------------------------- /src/components/IncomeExpenses.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 Expense Tracker 2 | 3 | An expense tracker app built with Vue 3 and the composition API. 4 | 5 | - Add and remove expenses/income 6 | - Track balance 7 | - Save data to local storage 8 | - [Vue Toastification](https://github.com/Maronato/vue-toastification) for notifications 9 | - ` 33 | -------------------------------------------------------------------------------- /src/components/AddTransaction.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 55 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 94 | -------------------------------------------------------------------------------- /src/assets/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Lato&display=swap'); 2 | 3 | :root { 4 | --box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); 5 | } 6 | 7 | * { 8 | box-sizing: border-box; 9 | } 10 | 11 | body { 12 | background-color: #f7f7f7; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | min-height: 100vh; 18 | margin: 0; 19 | font-family: 'Lato', sans-serif; 20 | font-size: 18px; 21 | } 22 | 23 | .container { 24 | margin: 30px auto; 25 | width: 400px; 26 | } 27 | 28 | h1 { 29 | letter-spacing: 1px; 30 | margin: 0; 31 | } 32 | 33 | h3 { 34 | border-bottom: 1px solid #bbb; 35 | padding-bottom: 10px; 36 | margin: 40px 0 10px; 37 | } 38 | 39 | h4 { 40 | margin: 0; 41 | text-transform: uppercase; 42 | } 43 | 44 | .inc-exp-container { 45 | background-color: #fff; 46 | box-shadow: var(--box-shadow); 47 | padding: 20px; 48 | display: flex; 49 | justify-content: space-between; 50 | margin: 20px 0; 51 | } 52 | 53 | .inc-exp-container > div { 54 | flex: 1; 55 | text-align: center; 56 | } 57 | 58 | .inc-exp-container > div:first-of-type { 59 | border-right: 1px solid #dedede; 60 | } 61 | 62 | .money { 63 | font-size: 20px; 64 | letter-spacing: 1px; 65 | margin: 5px 0; 66 | } 67 | 68 | .money.plus { 69 | color: #2ecc71; 70 | } 71 | 72 | .money.minus { 73 | color: #c0392b; 74 | } 75 | 76 | label { 77 | display: inline-block; 78 | margin: 10px 0; 79 | } 80 | 81 | input[type='text'], 82 | input[type='number'] { 83 | border: 1px solid #dedede; 84 | border-radius: 2px; 85 | display: block; 86 | font-size: 16px; 87 | padding: 10px; 88 | width: 100%; 89 | } 90 | 91 | .btn { 92 | cursor: pointer; 93 | background-color: #9c88ff; 94 | box-shadow: var(--box-shadow); 95 | color: #fff; 96 | border: 0; 97 | display: block; 98 | font-size: 16px; 99 | margin: 10px 0 30px; 100 | padding: 10px; 101 | width: 100%; 102 | } 103 | 104 | .btn:focus, 105 | .delete-btn:focus { 106 | outline: 0; 107 | } 108 | 109 | .list { 110 | list-style-type: none; 111 | padding: 0; 112 | margin-bottom: 40px; 113 | } 114 | 115 | .list li { 116 | background-color: #fff; 117 | box-shadow: var(--box-shadow); 118 | color: #333; 119 | display: flex; 120 | justify-content: space-between; 121 | position: relative; 122 | padding: 10px; 123 | margin: 10px 0; 124 | } 125 | 126 | .list li.plus { 127 | border-right: 5px solid #2ecc71; 128 | } 129 | 130 | .list li.minus { 131 | border-right: 5px solid #c0392b; 132 | } 133 | 134 | .delete-btn { 135 | cursor: pointer; 136 | background-color: #e74c3c; 137 | border: 0; 138 | color: #fff; 139 | font-size: 20px; 140 | line-height: 20px; 141 | padding: 2px 5px; 142 | position: absolute; 143 | top: 50%; 144 | left: 0; 145 | transform: translate(-100%, -50%); 146 | opacity: 0; 147 | transition: opacity 0.3s ease; 148 | } 149 | 150 | .list li:hover .delete-btn { 151 | opacity: 1; 152 | } 153 | --------------------------------------------------------------------------------