├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── screen.png ├── src ├── App.vue ├── assets │ └── style.css ├── components │ ├── AddTransaction.vue │ ├── Balance.vue │ ├── Header.vue │ ├── IncomeExpenses.vue │ └── TransactionList.vue └── main.js └── vite.config.js /.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 | -------------------------------------------------------------------------------- /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 | - ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-expense-tracker", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "vue-expense-tracker", 9 | "version": "0.0.0", 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 | "node_modules/@babel/parser": { 20 | "version": "7.23.0", 21 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", 22 | "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", 23 | "bin": { 24 | "parser": "bin/babel-parser.js" 25 | }, 26 | "engines": { 27 | "node": ">=6.0.0" 28 | } 29 | }, 30 | "node_modules/@esbuild/android-arm": { 31 | "version": "0.18.20", 32 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", 33 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", 34 | "cpu": [ 35 | "arm" 36 | ], 37 | "dev": true, 38 | "optional": true, 39 | "os": [ 40 | "android" 41 | ], 42 | "engines": { 43 | "node": ">=12" 44 | } 45 | }, 46 | "node_modules/@esbuild/android-arm64": { 47 | "version": "0.18.20", 48 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", 49 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", 50 | "cpu": [ 51 | "arm64" 52 | ], 53 | "dev": true, 54 | "optional": true, 55 | "os": [ 56 | "android" 57 | ], 58 | "engines": { 59 | "node": ">=12" 60 | } 61 | }, 62 | "node_modules/@esbuild/android-x64": { 63 | "version": "0.18.20", 64 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", 65 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", 66 | "cpu": [ 67 | "x64" 68 | ], 69 | "dev": true, 70 | "optional": true, 71 | "os": [ 72 | "android" 73 | ], 74 | "engines": { 75 | "node": ">=12" 76 | } 77 | }, 78 | "node_modules/@esbuild/darwin-arm64": { 79 | "version": "0.18.20", 80 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", 81 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", 82 | "cpu": [ 83 | "arm64" 84 | ], 85 | "dev": true, 86 | "optional": true, 87 | "os": [ 88 | "darwin" 89 | ], 90 | "engines": { 91 | "node": ">=12" 92 | } 93 | }, 94 | "node_modules/@esbuild/darwin-x64": { 95 | "version": "0.18.20", 96 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", 97 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", 98 | "cpu": [ 99 | "x64" 100 | ], 101 | "dev": true, 102 | "optional": true, 103 | "os": [ 104 | "darwin" 105 | ], 106 | "engines": { 107 | "node": ">=12" 108 | } 109 | }, 110 | "node_modules/@esbuild/freebsd-arm64": { 111 | "version": "0.18.20", 112 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", 113 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", 114 | "cpu": [ 115 | "arm64" 116 | ], 117 | "dev": true, 118 | "optional": true, 119 | "os": [ 120 | "freebsd" 121 | ], 122 | "engines": { 123 | "node": ">=12" 124 | } 125 | }, 126 | "node_modules/@esbuild/freebsd-x64": { 127 | "version": "0.18.20", 128 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", 129 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", 130 | "cpu": [ 131 | "x64" 132 | ], 133 | "dev": true, 134 | "optional": true, 135 | "os": [ 136 | "freebsd" 137 | ], 138 | "engines": { 139 | "node": ">=12" 140 | } 141 | }, 142 | "node_modules/@esbuild/linux-arm": { 143 | "version": "0.18.20", 144 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", 145 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", 146 | "cpu": [ 147 | "arm" 148 | ], 149 | "dev": true, 150 | "optional": true, 151 | "os": [ 152 | "linux" 153 | ], 154 | "engines": { 155 | "node": ">=12" 156 | } 157 | }, 158 | "node_modules/@esbuild/linux-arm64": { 159 | "version": "0.18.20", 160 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", 161 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", 162 | "cpu": [ 163 | "arm64" 164 | ], 165 | "dev": true, 166 | "optional": true, 167 | "os": [ 168 | "linux" 169 | ], 170 | "engines": { 171 | "node": ">=12" 172 | } 173 | }, 174 | "node_modules/@esbuild/linux-ia32": { 175 | "version": "0.18.20", 176 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", 177 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", 178 | "cpu": [ 179 | "ia32" 180 | ], 181 | "dev": true, 182 | "optional": true, 183 | "os": [ 184 | "linux" 185 | ], 186 | "engines": { 187 | "node": ">=12" 188 | } 189 | }, 190 | "node_modules/@esbuild/linux-loong64": { 191 | "version": "0.18.20", 192 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", 193 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", 194 | "cpu": [ 195 | "loong64" 196 | ], 197 | "dev": true, 198 | "optional": true, 199 | "os": [ 200 | "linux" 201 | ], 202 | "engines": { 203 | "node": ">=12" 204 | } 205 | }, 206 | "node_modules/@esbuild/linux-mips64el": { 207 | "version": "0.18.20", 208 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", 209 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", 210 | "cpu": [ 211 | "mips64el" 212 | ], 213 | "dev": true, 214 | "optional": true, 215 | "os": [ 216 | "linux" 217 | ], 218 | "engines": { 219 | "node": ">=12" 220 | } 221 | }, 222 | "node_modules/@esbuild/linux-ppc64": { 223 | "version": "0.18.20", 224 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", 225 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", 226 | "cpu": [ 227 | "ppc64" 228 | ], 229 | "dev": true, 230 | "optional": true, 231 | "os": [ 232 | "linux" 233 | ], 234 | "engines": { 235 | "node": ">=12" 236 | } 237 | }, 238 | "node_modules/@esbuild/linux-riscv64": { 239 | "version": "0.18.20", 240 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", 241 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", 242 | "cpu": [ 243 | "riscv64" 244 | ], 245 | "dev": true, 246 | "optional": true, 247 | "os": [ 248 | "linux" 249 | ], 250 | "engines": { 251 | "node": ">=12" 252 | } 253 | }, 254 | "node_modules/@esbuild/linux-s390x": { 255 | "version": "0.18.20", 256 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", 257 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", 258 | "cpu": [ 259 | "s390x" 260 | ], 261 | "dev": true, 262 | "optional": true, 263 | "os": [ 264 | "linux" 265 | ], 266 | "engines": { 267 | "node": ">=12" 268 | } 269 | }, 270 | "node_modules/@esbuild/linux-x64": { 271 | "version": "0.18.20", 272 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", 273 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", 274 | "cpu": [ 275 | "x64" 276 | ], 277 | "dev": true, 278 | "optional": true, 279 | "os": [ 280 | "linux" 281 | ], 282 | "engines": { 283 | "node": ">=12" 284 | } 285 | }, 286 | "node_modules/@esbuild/netbsd-x64": { 287 | "version": "0.18.20", 288 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", 289 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", 290 | "cpu": [ 291 | "x64" 292 | ], 293 | "dev": true, 294 | "optional": true, 295 | "os": [ 296 | "netbsd" 297 | ], 298 | "engines": { 299 | "node": ">=12" 300 | } 301 | }, 302 | "node_modules/@esbuild/openbsd-x64": { 303 | "version": "0.18.20", 304 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", 305 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", 306 | "cpu": [ 307 | "x64" 308 | ], 309 | "dev": true, 310 | "optional": true, 311 | "os": [ 312 | "openbsd" 313 | ], 314 | "engines": { 315 | "node": ">=12" 316 | } 317 | }, 318 | "node_modules/@esbuild/sunos-x64": { 319 | "version": "0.18.20", 320 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", 321 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", 322 | "cpu": [ 323 | "x64" 324 | ], 325 | "dev": true, 326 | "optional": true, 327 | "os": [ 328 | "sunos" 329 | ], 330 | "engines": { 331 | "node": ">=12" 332 | } 333 | }, 334 | "node_modules/@esbuild/win32-arm64": { 335 | "version": "0.18.20", 336 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", 337 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", 338 | "cpu": [ 339 | "arm64" 340 | ], 341 | "dev": true, 342 | "optional": true, 343 | "os": [ 344 | "win32" 345 | ], 346 | "engines": { 347 | "node": ">=12" 348 | } 349 | }, 350 | "node_modules/@esbuild/win32-ia32": { 351 | "version": "0.18.20", 352 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", 353 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", 354 | "cpu": [ 355 | "ia32" 356 | ], 357 | "dev": true, 358 | "optional": true, 359 | "os": [ 360 | "win32" 361 | ], 362 | "engines": { 363 | "node": ">=12" 364 | } 365 | }, 366 | "node_modules/@esbuild/win32-x64": { 367 | "version": "0.18.20", 368 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", 369 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", 370 | "cpu": [ 371 | "x64" 372 | ], 373 | "dev": true, 374 | "optional": true, 375 | "os": [ 376 | "win32" 377 | ], 378 | "engines": { 379 | "node": ">=12" 380 | } 381 | }, 382 | "node_modules/@jridgewell/sourcemap-codec": { 383 | "version": "1.4.15", 384 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 385 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" 386 | }, 387 | "node_modules/@vitejs/plugin-vue": { 388 | "version": "4.4.0", 389 | "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.4.0.tgz", 390 | "integrity": "sha512-xdguqb+VUwiRpSg+nsc2HtbAUSGak25DXYvpQQi4RVU1Xq1uworyoH/md9Rfd8zMmPR/pSghr309QNcftUVseg==", 391 | "dev": true, 392 | "engines": { 393 | "node": "^14.18.0 || >=16.0.0" 394 | }, 395 | "peerDependencies": { 396 | "vite": "^4.0.0", 397 | "vue": "^3.2.25" 398 | } 399 | }, 400 | "node_modules/@vue/compiler-core": { 401 | "version": "3.3.8", 402 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.8.tgz", 403 | "integrity": "sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==", 404 | "dependencies": { 405 | "@babel/parser": "^7.23.0", 406 | "@vue/shared": "3.3.8", 407 | "estree-walker": "^2.0.2", 408 | "source-map-js": "^1.0.2" 409 | } 410 | }, 411 | "node_modules/@vue/compiler-dom": { 412 | "version": "3.3.8", 413 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.8.tgz", 414 | "integrity": "sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==", 415 | "dependencies": { 416 | "@vue/compiler-core": "3.3.8", 417 | "@vue/shared": "3.3.8" 418 | } 419 | }, 420 | "node_modules/@vue/compiler-sfc": { 421 | "version": "3.3.8", 422 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.8.tgz", 423 | "integrity": "sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==", 424 | "dependencies": { 425 | "@babel/parser": "^7.23.0", 426 | "@vue/compiler-core": "3.3.8", 427 | "@vue/compiler-dom": "3.3.8", 428 | "@vue/compiler-ssr": "3.3.8", 429 | "@vue/reactivity-transform": "3.3.8", 430 | "@vue/shared": "3.3.8", 431 | "estree-walker": "^2.0.2", 432 | "magic-string": "^0.30.5", 433 | "postcss": "^8.4.31", 434 | "source-map-js": "^1.0.2" 435 | } 436 | }, 437 | "node_modules/@vue/compiler-ssr": { 438 | "version": "3.3.8", 439 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.8.tgz", 440 | "integrity": "sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==", 441 | "dependencies": { 442 | "@vue/compiler-dom": "3.3.8", 443 | "@vue/shared": "3.3.8" 444 | } 445 | }, 446 | "node_modules/@vue/reactivity": { 447 | "version": "3.3.8", 448 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.8.tgz", 449 | "integrity": "sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw==", 450 | "dependencies": { 451 | "@vue/shared": "3.3.8" 452 | } 453 | }, 454 | "node_modules/@vue/reactivity-transform": { 455 | "version": "3.3.8", 456 | "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.8.tgz", 457 | "integrity": "sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==", 458 | "dependencies": { 459 | "@babel/parser": "^7.23.0", 460 | "@vue/compiler-core": "3.3.8", 461 | "@vue/shared": "3.3.8", 462 | "estree-walker": "^2.0.2", 463 | "magic-string": "^0.30.5" 464 | } 465 | }, 466 | "node_modules/@vue/runtime-core": { 467 | "version": "3.3.8", 468 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.8.tgz", 469 | "integrity": "sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw==", 470 | "dependencies": { 471 | "@vue/reactivity": "3.3.8", 472 | "@vue/shared": "3.3.8" 473 | } 474 | }, 475 | "node_modules/@vue/runtime-dom": { 476 | "version": "3.3.8", 477 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.8.tgz", 478 | "integrity": "sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA==", 479 | "dependencies": { 480 | "@vue/runtime-core": "3.3.8", 481 | "@vue/shared": "3.3.8", 482 | "csstype": "^3.1.2" 483 | } 484 | }, 485 | "node_modules/@vue/server-renderer": { 486 | "version": "3.3.8", 487 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.8.tgz", 488 | "integrity": "sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg==", 489 | "dependencies": { 490 | "@vue/compiler-ssr": "3.3.8", 491 | "@vue/shared": "3.3.8" 492 | }, 493 | "peerDependencies": { 494 | "vue": "3.3.8" 495 | } 496 | }, 497 | "node_modules/@vue/shared": { 498 | "version": "3.3.8", 499 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz", 500 | "integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==" 501 | }, 502 | "node_modules/csstype": { 503 | "version": "3.1.2", 504 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", 505 | "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" 506 | }, 507 | "node_modules/esbuild": { 508 | "version": "0.18.20", 509 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", 510 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", 511 | "dev": true, 512 | "hasInstallScript": true, 513 | "bin": { 514 | "esbuild": "bin/esbuild" 515 | }, 516 | "engines": { 517 | "node": ">=12" 518 | }, 519 | "optionalDependencies": { 520 | "@esbuild/android-arm": "0.18.20", 521 | "@esbuild/android-arm64": "0.18.20", 522 | "@esbuild/android-x64": "0.18.20", 523 | "@esbuild/darwin-arm64": "0.18.20", 524 | "@esbuild/darwin-x64": "0.18.20", 525 | "@esbuild/freebsd-arm64": "0.18.20", 526 | "@esbuild/freebsd-x64": "0.18.20", 527 | "@esbuild/linux-arm": "0.18.20", 528 | "@esbuild/linux-arm64": "0.18.20", 529 | "@esbuild/linux-ia32": "0.18.20", 530 | "@esbuild/linux-loong64": "0.18.20", 531 | "@esbuild/linux-mips64el": "0.18.20", 532 | "@esbuild/linux-ppc64": "0.18.20", 533 | "@esbuild/linux-riscv64": "0.18.20", 534 | "@esbuild/linux-s390x": "0.18.20", 535 | "@esbuild/linux-x64": "0.18.20", 536 | "@esbuild/netbsd-x64": "0.18.20", 537 | "@esbuild/openbsd-x64": "0.18.20", 538 | "@esbuild/sunos-x64": "0.18.20", 539 | "@esbuild/win32-arm64": "0.18.20", 540 | "@esbuild/win32-ia32": "0.18.20", 541 | "@esbuild/win32-x64": "0.18.20" 542 | } 543 | }, 544 | "node_modules/estree-walker": { 545 | "version": "2.0.2", 546 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 547 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 548 | }, 549 | "node_modules/fsevents": { 550 | "version": "2.3.3", 551 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 552 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 553 | "dev": true, 554 | "hasInstallScript": true, 555 | "optional": true, 556 | "os": [ 557 | "darwin" 558 | ], 559 | "engines": { 560 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 561 | } 562 | }, 563 | "node_modules/magic-string": { 564 | "version": "0.30.5", 565 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", 566 | "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", 567 | "dependencies": { 568 | "@jridgewell/sourcemap-codec": "^1.4.15" 569 | }, 570 | "engines": { 571 | "node": ">=12" 572 | } 573 | }, 574 | "node_modules/nanoid": { 575 | "version": "3.3.7", 576 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 577 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 578 | "funding": [ 579 | { 580 | "type": "github", 581 | "url": "https://github.com/sponsors/ai" 582 | } 583 | ], 584 | "bin": { 585 | "nanoid": "bin/nanoid.cjs" 586 | }, 587 | "engines": { 588 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 589 | } 590 | }, 591 | "node_modules/picocolors": { 592 | "version": "1.0.0", 593 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 594 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" 595 | }, 596 | "node_modules/postcss": { 597 | "version": "8.4.31", 598 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", 599 | "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", 600 | "funding": [ 601 | { 602 | "type": "opencollective", 603 | "url": "https://opencollective.com/postcss/" 604 | }, 605 | { 606 | "type": "tidelift", 607 | "url": "https://tidelift.com/funding/github/npm/postcss" 608 | }, 609 | { 610 | "type": "github", 611 | "url": "https://github.com/sponsors/ai" 612 | } 613 | ], 614 | "dependencies": { 615 | "nanoid": "^3.3.6", 616 | "picocolors": "^1.0.0", 617 | "source-map-js": "^1.0.2" 618 | }, 619 | "engines": { 620 | "node": "^10 || ^12 || >=14" 621 | } 622 | }, 623 | "node_modules/rollup": { 624 | "version": "3.29.4", 625 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", 626 | "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", 627 | "dev": true, 628 | "bin": { 629 | "rollup": "dist/bin/rollup" 630 | }, 631 | "engines": { 632 | "node": ">=14.18.0", 633 | "npm": ">=8.0.0" 634 | }, 635 | "optionalDependencies": { 636 | "fsevents": "~2.3.2" 637 | } 638 | }, 639 | "node_modules/source-map-js": { 640 | "version": "1.0.2", 641 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 642 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 643 | "engines": { 644 | "node": ">=0.10.0" 645 | } 646 | }, 647 | "node_modules/vite": { 648 | "version": "4.5.0", 649 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", 650 | "integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==", 651 | "dev": true, 652 | "dependencies": { 653 | "esbuild": "^0.18.10", 654 | "postcss": "^8.4.27", 655 | "rollup": "^3.27.1" 656 | }, 657 | "bin": { 658 | "vite": "bin/vite.js" 659 | }, 660 | "engines": { 661 | "node": "^14.18.0 || >=16.0.0" 662 | }, 663 | "funding": { 664 | "url": "https://github.com/vitejs/vite?sponsor=1" 665 | }, 666 | "optionalDependencies": { 667 | "fsevents": "~2.3.2" 668 | }, 669 | "peerDependencies": { 670 | "@types/node": ">= 14", 671 | "less": "*", 672 | "lightningcss": "^1.21.0", 673 | "sass": "*", 674 | "stylus": "*", 675 | "sugarss": "*", 676 | "terser": "^5.4.0" 677 | }, 678 | "peerDependenciesMeta": { 679 | "@types/node": { 680 | "optional": true 681 | }, 682 | "less": { 683 | "optional": true 684 | }, 685 | "lightningcss": { 686 | "optional": true 687 | }, 688 | "sass": { 689 | "optional": true 690 | }, 691 | "stylus": { 692 | "optional": true 693 | }, 694 | "sugarss": { 695 | "optional": true 696 | }, 697 | "terser": { 698 | "optional": true 699 | } 700 | } 701 | }, 702 | "node_modules/vue": { 703 | "version": "3.3.8", 704 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.8.tgz", 705 | "integrity": "sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==", 706 | "dependencies": { 707 | "@vue/compiler-dom": "3.3.8", 708 | "@vue/compiler-sfc": "3.3.8", 709 | "@vue/runtime-dom": "3.3.8", 710 | "@vue/server-renderer": "3.3.8", 711 | "@vue/shared": "3.3.8" 712 | }, 713 | "peerDependencies": { 714 | "typescript": "*" 715 | }, 716 | "peerDependenciesMeta": { 717 | "typescript": { 718 | "optional": true 719 | } 720 | } 721 | }, 722 | "node_modules/vue-toastification": { 723 | "version": "2.0.0-rc.5", 724 | "resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-2.0.0-rc.5.tgz", 725 | "integrity": "sha512-q73e5jy6gucEO/U+P48hqX+/qyXDozAGmaGgLFm5tXX4wJBcVsnGp4e/iJqlm9xzHETYOilUuwOUje2Qg1JdwA==", 726 | "peerDependencies": { 727 | "vue": "^3.0.2" 728 | } 729 | } 730 | } 731 | } 732 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradtraversy/vue-expense-tracker/f5fdaebf31543f9831fe377df438e43dac05c3c2/public/favicon.ico -------------------------------------------------------------------------------- /public/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradtraversy/vue-expense-tracker/f5fdaebf31543f9831fe377df438e43dac05c3c2/public/screen.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/AddTransaction.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 55 | -------------------------------------------------------------------------------- /src/components/Balance.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | -------------------------------------------------------------------------------- /src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/components/IncomeExpenses.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 28 | -------------------------------------------------------------------------------- /src/components/TransactionList.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------