Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create finance tracker #4956

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions finance tracker
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
vue create personal-finance-tracker
cd personal-finance-tracker
npm install vue-router vuex chart.js vue-chartjs
Comment on lines +1 to +3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Pin dependency versions for better reproducibility.

Installing dependencies without version specifications can lead to compatibility issues. Consider pinning specific versions:

-npm install vue-router vuex chart.js vue-chartjs
+npm install [email protected] [email protected] [email protected] [email protected]

Committable suggestion skipped: line range outside the PR's diff.


import Vue from 'vue';
import Router from 'vue-router';
import Home from '../views/Home.vue';
import Login from '../views/Login.vue';
import Register from '../views/Register.vue';

Vue.use(Router);

const routes = [
{ path: '/', component: Home },
{ path: '/login', component: Login },
{ path: '/register', component: Register },
];

const router = new Router({
mode: 'history',
routes,
});
Comment on lines +13 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance router configuration with authentication and error handling.

The current router configuration lacks several important features:

  1. Authentication guards for protected routes
  2. 404 route handler for invalid paths
  3. Route meta information for auth requirements
 const routes = [
-  { path: '/', component: Home },
-  { path: '/login', component: Login },
-  { path: '/register', component: Register },
+  { 
+    path: '/', 
+    component: Home,
+    meta: { requiresAuth: true }
+  },
+  { 
+    path: '/login',
+    component: Login,
+    meta: { guest: true }
+  },
+  { 
+    path: '/register',
+    component: Register,
+    meta: { guest: true }
+  },
+  { 
+    path: '*',
+    component: () => import('../views/NotFound.vue')
+  }
 ];

 const router = new Router({
   mode: 'history',
   routes,
 });
+
+router.beforeEach((to, from, next) => {
+  const isAuthenticated = store.getters.isAuthenticated;
+  
+  if (to.matched.some(record => record.meta.requiresAuth)) {
+    if (!isAuthenticated) {
+      next('/login');
+    } else {
+      next();
+    }
+  } else if (to.matched.some(record => record.meta.guest)) {
+    if (isAuthenticated) {
+      next('/');
+    } else {
+      next();
+    }
+  } else {
+    next();
+  }
+});

Committable suggestion skipped: line range outside the PR's diff.


export default router;

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
state: {
transactions: [],
},
mutations: {
ADD_TRANSACTION(state, transaction) {
state.transactions.push(transaction);
},
DELETE_TRANSACTION(state, transactionId) {
state.transactions = state.transactions.filter(t => t.id !== transactionId);
},
},
actions: {
addTransaction({ commit }, transaction) {
commit('ADD_TRANSACTION', transaction);
},
deleteTransaction({ commit }, transactionId) {
commit('DELETE_TRANSACTION', transactionId);
},
},
getters: {
allTransactions: state => state.transactions,
},
});
Comment on lines +31 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance store with persistence, validation, and error handling.

The current store implementation lacks several critical features:

  1. Data persistence
  2. Transaction validation
  3. Loading states
  4. Error handling
 export default new Vuex.Store({
   state: {
     transactions: [],
+    loading: false,
+    error: null
   },
   mutations: {
     ADD_TRANSACTION(state, transaction) {
+      if (!transaction.amount || !transaction.category) {
+        throw new Error('Invalid transaction data');
+      }
       state.transactions.push(transaction);
+      localStorage.setItem('transactions', JSON.stringify(state.transactions));
     },
     DELETE_TRANSACTION(state, transactionId) {
       state.transactions = state.transactions.filter(t => t.id !== transactionId);
+      localStorage.setItem('transactions', JSON.stringify(state.transactions));
     },
+    SET_LOADING(state, status) {
+      state.loading = status;
+    },
+    SET_ERROR(state, error) {
+      state.error = error;
+    }
   },
   actions: {
-    addTransaction({ commit }, transaction) {
-      commit('ADD_TRANSACTION', transaction);
+    async addTransaction({ commit }, transaction) {
+      try {
+        commit('SET_LOADING', true);
+        commit('SET_ERROR', null);
+        // Add API call here when backend is ready
+        commit('ADD_TRANSACTION', transaction);
+      } catch (error) {
+        commit('SET_ERROR', error.message);
+      } finally {
+        commit('SET_LOADING', false);
+      }
     },
-    deleteTransaction({ commit }, transactionId) {
-      commit('DELETE_TRANSACTION', transactionId);
+    async deleteTransaction({ commit }, transactionId) {
+      try {
+        commit('SET_LOADING', true);
+        commit('SET_ERROR', null);
+        // Add API call here when backend is ready
+        commit('DELETE_TRANSACTION', transactionId);
+      } catch (error) {
+        commit('SET_ERROR', error.message);
+      } finally {
+        commit('SET_LOADING', false);
+      }
     },
+    initializeStore({ commit }) {
+      const stored = localStorage.getItem('transactions');
+      if (stored) {
+        commit('SET_TRANSACTIONS', JSON.parse(stored));
+      }
+    }
   },
   getters: {
     allTransactions: state => state.transactions,
+    isLoading: state => state.loading,
+    hasError: state => state.error !== null,
+    getError: state => state.error
   },
 });

Committable suggestion skipped: line range outside the PR's diff.


<template>
<form @submit.prevent="submitTransaction">
<input v-model="amount" type="number" placeholder="Amount" required />
<input v-model="category" type="text" placeholder="Category" required />
<button type="submit">Add Transaction</button>
</form>
</template>

<script>
export default {
data() {
return {
amount: '',
category: '',
};
},
methods: {
submitTransaction() {
const transaction = {
id: Date.now(),
amount: parseFloat(this.amount),
category: this.category,
};
this.$store.dispatch('addTransaction', transaction);
this.amount = '';
this.category = '';
},
},
};
</script>


<template>
<form @submit.prevent="submitTransaction">
<input v-model="amount" type="number" placeholder="Amount" required />
<input v-model="category" type="text" placeholder="Category" required />
<button type="submit">Add Transaction</button>
</form>
</template>

<script>
export default {
data() {
return {
amount: '',
category: '',
};
},
methods: {
submitTransaction() {
const transaction = {
id: Date.now(),
amount: parseFloat(this.amount),
category: this.category,
};
this.$store.dispatch('addTransaction', transaction);
this.amount = '';
this.category = '';
},
},
};
</script>
Comment on lines +56 to +117
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Remove duplicated component and enhance form validation.

There are two identical form components which violates the DRY principle. Additionally, the form implementation has several issues:

  1. Weak ID generation using Date.now()
  2. Basic input validation
  3. No user feedback
  4. No loading states

Keep one component and enhance it:

 <template>
   <form @submit.prevent="submitTransaction">
+    <div v-if="error" class="error">{{ error }}</div>
     <input 
       v-model.number="amount" 
       type="number" 
       placeholder="Amount" 
+      :disabled="loading"
+      min="0.01"
+      step="0.01"
       required 
     />
     <input 
       v-model="category" 
       type="text" 
       placeholder="Category" 
+      :disabled="loading"
+      pattern="[A-Za-z ]+"
+      title="Only letters and spaces allowed"
       required 
     />
-    <button type="submit">Add Transaction</button>
+    <button type="submit" :disabled="loading">
+      {{ loading ? 'Adding...' : 'Add Transaction' }}
+    </button>
   </form>
 </template>

 <script>
+import { v4 as uuidv4 } from 'uuid';
+
 export default {
   data() {
     return {
       amount: '',
       category: '',
+      error: null
     };
   },
+  computed: {
+    loading() {
+      return this.$store.getters.isLoading;
+    }
+  },
   methods: {
-    submitTransaction() {
+    async submitTransaction() {
+      this.error = null;
+      
+      if (this.amount <= 0) {
+        this.error = 'Amount must be greater than 0';
+        return;
+      }
+      
       const transaction = {
-        id: Date.now(),
+        id: uuidv4(),
         amount: parseFloat(this.amount),
         category: this.category,
+        timestamp: new Date().toISOString()
       };
-      this.$store.dispatch('addTransaction', transaction);
-      this.amount = '';
-      this.category = '';
+      
+      try {
+        await this.$store.dispatch('addTransaction', transaction);
+        this.amount = '';
+        this.category = '';
+        this.$emit('transaction-added', transaction);
+      } catch (error) {
+        this.error = error.message;
+      }
     },
   },
 };
 </script>

+<style scoped>
+.error {
+  color: red;
+  margin-bottom: 1rem;
+}
+</style>

Consider extracting the validation logic into a separate utility function for reusability.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<template>
<form @submit.prevent="submitTransaction">
<input v-model="amount" type="number" placeholder="Amount" required />
<input v-model="category" type="text" placeholder="Category" required />
<button type="submit">Add Transaction</button>
</form>
</template>
<script>
export default {
data() {
return {
amount: '',
category: '',
};
},
methods: {
submitTransaction() {
const transaction = {
id: Date.now(),
amount: parseFloat(this.amount),
category: this.category,
};
this.$store.dispatch('addTransaction', transaction);
this.amount = '';
this.category = '';
},
},
};
</script>
<template>
<form @submit.prevent="submitTransaction">
<input v-model="amount" type="number" placeholder="Amount" required />
<input v-model="category" type="text" placeholder="Category" required />
<button type="submit">Add Transaction</button>
</form>
</template>
<script>
export default {
data() {
return {
amount: '',
category: '',
};
},
methods: {
submitTransaction() {
const transaction = {
id: Date.now(),
amount: parseFloat(this.amount),
category: this.category,
};
this.$store.dispatch('addTransaction', transaction);
this.amount = '';
this.category = '';
},
},
};
</script>
<template>
<form @submit.prevent="submitTransaction">
<div v-if="error" class="error">{{ error }}</div>
<input
v-model.number="amount"
type="number"
placeholder="Amount"
:disabled="loading"
min="0.01"
step="0.01"
required
/>
<input
v-model="category"
type="text"
placeholder="Category"
:disabled="loading"
pattern="[A-Za-z ]+"
title="Only letters and spaces allowed"
required
/>
<button type="submit" :disabled="loading">
{{ loading ? 'Adding...' : 'Add Transaction' }}
</button>
</form>
</template>
<script>
import { v4 as uuidv4 } from 'uuid';
export default {
data() {
return {
amount: '',
category: '',
error: null
};
},
computed: {
loading() {
return this.$store.getters.isLoading;
}
},
methods: {
async submitTransaction() {
this.error = null;
if (this.amount <= 0) {
this.error = 'Amount must be greater than 0';
return;
}
const transaction = {
id: uuidv4(),
amount: parseFloat(this.amount),
category: this.category,
timestamp: new Date().toISOString()
};
try {
await this.$store.dispatch('addTransaction', transaction);
this.amount = '';
this.category = '';
this.$emit('transaction-added', transaction);
} catch (error) {
this.error = error.message;
}
},
},
};
</script>
<style scoped>
.error {
color: red;
margin-bottom: 1rem;
}
</style>


<template>
<div>
<bar-chart :chart-data="chartData"></bar-chart>
</div>
</template>

<script>
import { Bar }
Comment on lines +119 to +126
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Complete the chart component implementation.

The chart component is incomplete. The import statement is cut off and chart configuration is missing.

Here's a complete implementation:

 <template>
   <div>
+    <div v-if="loading">Loading...</div>
+    <div v-else-if="error">{{ error }}</div>
     <bar-chart :chart-data="chartData"></bar-chart>
   </div>
 </template>

 <script>
-import { Bar }
+import { Bar } from 'vue-chartjs';
+import { 
+  Chart as ChartJS,
+  Title,
+  Tooltip,
+  Legend,
+  BarElement,
+  CategoryScale,
+  LinearScale
+} from 'chart.js';
+
+ChartJS.register(
+  Title,
+  Tooltip,
+  Legend,
+  BarElement,
+  CategoryScale,
+  LinearScale
+);
+
+export default {
+  components: {
+    BarChart: Bar
+  },
+  data() {
+    return {
+      loading: false,
+      error: null
+    };
+  },
+  computed: {
+    chartData() {
+      const transactions = this.$store.getters.allTransactions;
+      const categories = [...new Set(transactions.map(t => t.category))];
+      
+      return {
+        labels: categories,
+        datasets: [{
+          label: 'Spending by Category',
+          data: categories.map(category => 
+            transactions
+              .filter(t => t.category === category)
+              .reduce((sum, t) => sum + t.amount, 0)
+          ),
+          backgroundColor: 'rgba(75, 192, 192, 0.2)',
+          borderColor: 'rgba(75, 192, 192, 1)',
+          borderWidth: 1
+        }]
+      };
+    }
+  }
+};
+</script>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<template>
<div>
<bar-chart :chart-data="chartData"></bar-chart>
</div>
</template>
<script>
import { Bar }
<template>
<div>
<div v-if="loading">Loading...</div>
<div v-else-if="error">{{ error }}</div>
<bar-chart :chart-data="chartData"></bar-chart>
</div>
</template>
<script>
import { Bar } from 'vue-chartjs';
import {
Chart as ChartJS,
Title,
Tooltip,
Legend,
BarElement,
CategoryScale,
LinearScale
} from 'chart.js';
ChartJS.register(
Title,
Tooltip,
Legend,
BarElement,
CategoryScale,
LinearScale
);
export default {
components: {
BarChart: Bar
},
data() {
return {
loading: false,
error: null
};
},
computed: {
chartData() {
const transactions = this.$store.getters.allTransactions;
const categories = [...new Set(transactions.map(t => t.category))];
return {
labels: categories,
datasets: [{
label: 'Spending by Category',
data: categories.map(category =>
transactions
.filter(t => t.category === category)
.reduce((sum, t) => sum + t.amount, 0)
),
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
};
}
}
};
</script>

Loading