Skip to content

Commit 9144a59

Browse files
committed
add install app feature so that user can install the app as pwa
1 parent deb25a2 commit 9144a59

52 files changed

Lines changed: 1421 additions & 109 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# [📘 Expense Tracker](https://exwise.vercel.app/)
22

3-
>### Expense Wisely – Where Your Money Stops Playing Hide and Seek!
3+
> ### Expense Wisely – Where Your Money Stops Playing Hide and Seek!
44
55
## 📌 Overview
66

@@ -12,11 +12,11 @@ This application leverages **Angular** as the frontend framework and **Tailwind
1212

1313
## 🧰 Tech Stack
1414

15-
| Technology | Purpose |
16-
| ------------ | -------------------------- |
17-
| Angular | Frontend application logic |
18-
| Tailwind CSS | Styling and responsive UI |
19-
| LocalStorage | Persistent data storage |
15+
| Technology | Purpose |
16+
| -------------------- | ------------------------------- |
17+
| Angular | Frontend application logic |
18+
| Tailwind CSS | Styling and responsive UI |
19+
| LocalStorage | Persistent data storage |
2020

2121
---
2222

@@ -60,7 +60,7 @@ All expenses are stored in the device's **LocalStorage** and reflected immediate
6060
* Displays all user expenses in a scrollable, and sortable list.
6161
* Users can:
6262

63-
* 🔍 **Search** expenses quickly by typing keywords (category, note, or payment mode).
63+
* 🔍 **Search** expenses quickly by typing keywords (category, note, or payment mode).
6464
* 🧾 **Sort** expenses by date, amount, or category.
6565
* 🎯 **Filter** by category, date, extra spending, and payment mode.
6666
* 🖱️ Tap any entry to open a **modal** with complete details and options to **edit** or **delete** the expense.
@@ -99,7 +99,7 @@ Provides customization and utility options for better personalization:
9999

100100
* The application is specifically designed for **mobile devices**.
101101
* Features responsive components, intuitive touch controls, and visually appealing UI optimized for small screens.
102-
* Not intended for laptop or desktop usage.
102+
* Not intended for laptop or desktop usage (though it works as PWA).
103103

104104
---
105105

@@ -128,7 +128,7 @@ All user data is stored using the **browser’s LocalStorage API**, ensuring:
128128

129129
## 📄 Code Documentation
130130

131-
For developers and contributors, detailed code-level documentation is available here:
131+
For developers and contributors, detailed code-level documentation is available here:
132132
👉 [Documentation](https://devnamdev2003.github.io/angular-expense-tracker/documentation/)
133133

134134
---

documentation/components/AddExpenseComponent.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1591,7 +1591,7 @@ <h3 id="inputs">
15911591
<script src="../js/libs/deep-iterator.js"></script>
15921592
<script>
15931593
var COMPONENT_TEMPLATE = '<div><section id="add" class="section p-4 md:p-4"> <!-- <h1 class="text-2xl font-bold mb-6 text-[var(--color-accent)]">➕ Add Expense</h1> --> <form [formGroup]="expenseForm" (ngSubmit)="onSubmit()" class="space-y-4 text-[var(--color-text)]"> <!-- Amount and Category --> <div class="flex flex-col md:flex-row md:space-x-4"> <!-- Amount --> <div class="relative w-full md:w-1/2"> <label class="block mb-1 font-medium">Amount</label> <input #amountInput type="number" min="0" max="100000000" formControlName="amount" class="w-full p-2 rounded border" style="background-color: var(--input-bg); color: var(--input-text); border-color: var(--input-border);" step="0.01" /> <div class="error-message text-[var(--color-error)]" *ngIf="expenseForm.get(\'amount\')?.touched && expenseForm.get(\'amount\')?.errors?.[\'min\']"> Amount cannot be negative </div> <div class="error-message text-[var(--color-error)]" *ngIf="expenseForm.get(\'amount\')?.touched && expenseForm.get(\'amount\')?.errors?.[\'max\']"> Amount cannot exceed 100,000,000 </div> <div class="error-message text-[var(--color-error)]" *ngIf="expenseForm.get(\'amount\')?.touched && expenseForm.get(\'amount\')?.errors?.[\'required\']"> Amount is required </div> </div> <!-- Category --> <div class="w-full md:w-1/2 mt-4 md:mt-0"> <label class="block mb-1 font-medium">Category</label> <app-category-dropdown [dropdownMaxHeightClass]="\'max-h-60\'" [selectedCategoryName]="selectedCategoryName" (categorySelected)="onCategorySelected($event)"></app-category-dropdown> <div class="error-message text-[var(--color-error)]" *ngIf="expenseForm.get(\'category_id\')?.touched && expenseForm.get(\'category_id\')?.invalid"> Category is required </div> </div> </div> <!-- Date and Time --> <div class="flex flex-row space-x-2"> <div class="w-full md:w-1/2 flex flex-col"> <label class="block mb-1 font-medium">Date</label> <input type="date" formControlName="date" class="w-full p-2 rounded border bg-[var(--input-bg)] text-[var(--input-text)] border-[var(--input-border)]" /> <div *ngIf="expenseForm.get(\'date\')?.touched && expenseForm.get(\'date\')?.invalid" class="text-[var(--color-error)] text-sm mt-1"> Date is required </div> </div> <div class="w-full md:w-1/2 flex flex-col"> <label class="block mb-1 font-medium">Time</label> <input type="time" formControlName="time" step="1" class="w-full p-2 rounded border bg-[var(--input-bg)] text-[var(--input-text)] border-[var(--input-border)]" /> <div *ngIf="expenseForm.get(\'time\')?.touched && expenseForm.get(\'time\')?.invalid" class="text-[var(--color-error)] text-sm mt-1"> Time is required </div> </div> </div> <!-- Payment Mode and Location --> <div class="flex flex-col md:flex-row md:space-x-4"> <div class="w-full md:w-1/2"> <label class="block mb-1 font-medium">Payment Mode</label> <select formControlName="payment_mode" class="w-full p-2 rounded border" style="background-color: var(--input-bg); color: var(--input-text); border-color: var(--input-border);"> <option value="UPI">UPI</option> <option value="Cash">Cash</option> <option value="Others">Others</option> </select> </div> <div class="relative w-full md:w-1/2 mt-4 md:mt-0"> <label class="block mb-1 font-medium">Location</label> <input type="text" formControlName="location" maxlength="51" class="w-full p-2 rounded border" style="background-color: var(--input-bg); color: var(--input-text); border-color: var(--input-border);" /> <!-- Location Suggestions --> <ul *ngIf="showLocationSuggestions && filteredLocationSuggestions.length" class="absolute max-h-40 z-50 w-full overflow-y-auto bg-[var(--color-bg)] rounded border" style="background-color: var(--input-bg); color: var(--input-text); border-color: var(--input-border);"> <li *ngFor="let loc of filteredLocationSuggestions" (click)="selectLocationSuggestion(loc)" class="p-2 cursor-pointer hover:bg-[--list-hover]"> {{ loc }} </li> </ul> <div class="error-message text-[var(--color-error)]" *ngIf="expenseForm.get(\'location\')?.touched && expenseForm.get(\'location\')?.errors?.[\'maxlength\']"> Location cannot be more than 50 characters </div> </div> </div> <!-- Note --> <div> <label class="block mb-1 font-medium">Note</label> <textarea formControlName="note" maxlength="101" rows="2" class="w-full p-2 rounded border resize-none" style="background-color: var(--input-bg); color: var(--input-text); border-color: var(--input-border);"></textarea> <!-- Note Suggestions --> <ul *ngIf="showNoteSuggestions && filteredNoteSuggestions.length" class="max-h-40 z-50 w-full overflow-y-auto bg-[var(--color-bg)] rounded border" style="background-color: var(--input-bg); color: var(--input-text); border-color: var(--input-border);"> <li *ngFor="let note of filteredNoteSuggestions" (click)="selectNoteSuggestion(note)" class="p-2 cursor-pointer hover:bg-[--list-hover]"> {{ note }} </li> </ul> <div class="error-message text-[var(--color-error)]" *ngIf="expenseForm.get(\'note\')?.touched && expenseForm.get(\'note\')?.errors?.[\'maxlength\']"> Note cannot be more than 100 characters </div> </div> <!-- Extra Spending Toggle --> <div style="margin-top: 8px;"> <div class="w-full md:w-1/2 flex items-center justify-between md:justify-start md:space-x-4"> <label for="isExtraSpending" class="block font-medium">Mark as Extra Spending</label> <label class="relative inline-flex items-center cursor-pointer"> <input type="checkbox" id="isExtraSpending" formControlName="isExtraSpending" class="sr-only peer"> <div class="w-11 h-6 bg-gray-600 rounded-full peer peer-checked:bg-[var(--color-accent)] transition-all"> </div> <span class="absolute left-1 top-1 w-4 h-4 bg-white rounded-full transition-all peer-checked:translate-x-5"></span> </label> </div> </div> <div> <button type="submit" class="bg-[var(--color-accent)] text-[var(--color-white)] px-4 py-2 rounded w-full"> Add Expense </button> </div> </form></section></div>'
1594-
var COMPONENTS = [{'name': 'AddExpenseComponent', 'selector': 'app-add-expense'},{'name': 'AiComponent', 'selector': 'app-ai'},{'name': 'AnalysisComponent', 'selector': 'app-analysis'},{'name': 'AppComponent', 'selector': 'app-root'},{'name': 'BudgetComponent', 'selector': 'app-budget'},{'name': 'CalendarComponent', 'selector': 'app-calendar'},{'name': 'CategoryDropdownComponent', 'selector': 'app-category-dropdown'},{'name': 'CustomModalComponent', 'selector': 'app-custom-modal'},{'name': 'DownloadComponentComponent', 'selector': 'app-download-component'},{'name': 'ExpenseDetailsModalComponent', 'selector': 'app-expense-details-modal'},{'name': 'ExpenseListComponent', 'selector': 'app-expense-list'},{'name': 'FooterComponent', 'selector': 'app-footer'},{'name': 'FormModelComponent', 'selector': 'app-form-model'},{'name': 'GlobalLoaderComponent', 'selector': 'app-global-loader'},{'name': 'GraphsComponent', 'selector': 'app-graphs'},{'name': 'HamburgerMenuComponent', 'selector': 'app-hamburger-menu'},{'name': 'HomeComponent', 'selector': 'app-home'},{'name': 'InfoIconComponent', 'selector': 'app-info-icon'},{'name': 'ListExpensesComponent', 'selector': 'app-list-expenses'},{'name': 'MusicComponent', 'selector': 'app-music'},{'name': 'NavbarComponent', 'selector': 'app-navbar'},{'name': 'PieChartComponent', 'selector': 'app-pie-chart'},{'name': 'SearchButtonComponent', 'selector': 'app-search-button'},{'name': 'SearchComponent', 'selector': 'app-search'},{'name': 'SettingItemComponent', 'selector': 'app-setting-item'},{'name': 'SettingsComponent', 'selector': 'app-settings'},{'name': 'SidebarComponent', 'selector': 'app-sidebar'},{'name': 'TemplatePlaygroundComponent', 'selector': 'template-playground-root'},{'name': 'ToastComponent', 'selector': 'app-toast'}];
1594+
var COMPONENTS = [{'name': 'AddExpenseComponent', 'selector': 'app-add-expense'},{'name': 'AiComponent', 'selector': 'app-ai'},{'name': 'AnalysisComponent', 'selector': 'app-analysis'},{'name': 'AppComponent', 'selector': 'app-root'},{'name': 'BudgetComponent', 'selector': 'app-budget'},{'name': 'CalendarComponent', 'selector': 'app-calendar'},{'name': 'CategoryDropdownComponent', 'selector': 'app-category-dropdown'},{'name': 'CustomModalComponent', 'selector': 'app-custom-modal'},{'name': 'DownloadComponentComponent', 'selector': 'app-download-component'},{'name': 'ExpenseDetailsModalComponent', 'selector': 'app-expense-details-modal'},{'name': 'ExpenseListComponent', 'selector': 'app-expense-list'},{'name': 'FooterComponent', 'selector': 'app-footer'},{'name': 'FormModelComponent', 'selector': 'app-form-model'},{'name': 'GlobalLoaderComponent', 'selector': 'app-global-loader'},{'name': 'GraphsComponent', 'selector': 'app-graphs'},{'name': 'HamburgerMenuComponent', 'selector': 'app-hamburger-menu'},{'name': 'HomeComponent', 'selector': 'app-home'},{'name': 'InfoIconComponent', 'selector': 'app-info-icon'},{'name': 'InstallAppPopupComponentComponent', 'selector': 'app-install-app-popup-component'},{'name': 'ListExpensesComponent', 'selector': 'app-list-expenses'},{'name': 'MusicComponent', 'selector': 'app-music'},{'name': 'NavbarComponent', 'selector': 'app-navbar'},{'name': 'PieChartComponent', 'selector': 'app-pie-chart'},{'name': 'SearchButtonComponent', 'selector': 'app-search-button'},{'name': 'SearchComponent', 'selector': 'app-search'},{'name': 'SettingItemComponent', 'selector': 'app-setting-item'},{'name': 'SettingsComponent', 'selector': 'app-settings'},{'name': 'SidebarComponent', 'selector': 'app-sidebar'},{'name': 'TemplatePlaygroundComponent', 'selector': 'template-playground-root'},{'name': 'ToastComponent', 'selector': 'app-toast'}];
15951595
var DIRECTIVES = [];
15961596
var ACTUAL_COMPONENT = {'name': 'AddExpenseComponent'};
15971597
</script>

documentation/components/AiComponent.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ <h3 id="inputs">
622622
<script src="../js/libs/deep-iterator.js"></script>
623623
<script>
624624
var COMPONENT_TEMPLATE = '<div><section class="section p-4 mb-4"> <div class="transition-all duration-300 max-w-2xl mx-auto"> <div class="sticky top-0 z-30 bg-[var(--color-bg)] py-2 pb-4"> <h2 class="text-xl font-semibold mb-4 text-[var(--theme-color)] text-center">💬 AI Assistant </h2> <!-- Chat-style Input Area --> <div class="flex gap-2 items-center"> <!-- Send Button with Icon --> <!-- Input Field --> <input type="text" [(ngModel)]="userInput" placeholder="Ask something related to your expenses..." class="flex-1 px-4 py-2 rounded-lg border text-[var(--input-text)] bg-[var(--input-bg)] border-[var(--input-border)] focus:outline-none focus:ring-2 focus:ring-[var(--theme-color)] transition-all duration-200" /> <button (click)="send()" class="shrink-0 bg-[var(--theme-color)] p-3 rounded-full shadow hover:scale-105 transition-transform duration-200" title="Send"> <img src="assets/img/icon/icons8-send-48.png" alt="Send" class="w-5 h-5" style="filter:invert(100%)" /> </button> </div> </div> <!-- AI Response Box --> <div *ngIf="responseHtml" class="mt-1 p-1 pb-4 bg-[var(--color-bg)] animate-fade-in overflow-auto" [innerHTML]="responseHtml"></div> </div></section><!-- for chat --><!-- <section class="p-4 section"> <div class="p-4 max-w-xl mx-auto"> <div class="space-y-4 mb-4"> <div *ngFor="let msg of messages"> <div [ngClass]="{ \'text-right\': msg.role === \'user\', \'text-left\': msg.role === \'model\' }"> <div [ngClass]="{ \'bg-blue-100 text-blue-800\': msg.role === \'user\', \'bg-gray-100 text-gray-800\': msg.role === \'model\' }" class="inline-block px-4 py-2 rounded-lg max-w-full overflow-x-auto"> <ng-container *ngIf="msg.role === \'user\'; else markdownReply"> {{ msg.parts[0].text }} </ng-container> <ng-template #markdownReply> <div [innerHTML]="msg.safeHtml"></div> </ng-template> </div> </div> </div> <div *ngIf="loading" class="text-center text-gray-400">Thinking...</div> </div> <div class="flex gap-2"> <input type="text" [(ngModel)]="userInput" placeholder="Type your message..." class="w-full p-2 border rounded" (keydown.enter)="send()" /> <button (click)="send()" class="bg-blue-500 text-white px-4 py-2 rounded" [disabled]="loading"> Send </button> </div> </div></section> --></div>'
625-
var COMPONENTS = [{'name': 'AddExpenseComponent', 'selector': 'app-add-expense'},{'name': 'AiComponent', 'selector': 'app-ai'},{'name': 'AnalysisComponent', 'selector': 'app-analysis'},{'name': 'AppComponent', 'selector': 'app-root'},{'name': 'BudgetComponent', 'selector': 'app-budget'},{'name': 'CalendarComponent', 'selector': 'app-calendar'},{'name': 'CategoryDropdownComponent', 'selector': 'app-category-dropdown'},{'name': 'CustomModalComponent', 'selector': 'app-custom-modal'},{'name': 'DownloadComponentComponent', 'selector': 'app-download-component'},{'name': 'ExpenseDetailsModalComponent', 'selector': 'app-expense-details-modal'},{'name': 'ExpenseListComponent', 'selector': 'app-expense-list'},{'name': 'FooterComponent', 'selector': 'app-footer'},{'name': 'FormModelComponent', 'selector': 'app-form-model'},{'name': 'GlobalLoaderComponent', 'selector': 'app-global-loader'},{'name': 'GraphsComponent', 'selector': 'app-graphs'},{'name': 'HamburgerMenuComponent', 'selector': 'app-hamburger-menu'},{'name': 'HomeComponent', 'selector': 'app-home'},{'name': 'InfoIconComponent', 'selector': 'app-info-icon'},{'name': 'ListExpensesComponent', 'selector': 'app-list-expenses'},{'name': 'MusicComponent', 'selector': 'app-music'},{'name': 'NavbarComponent', 'selector': 'app-navbar'},{'name': 'PieChartComponent', 'selector': 'app-pie-chart'},{'name': 'SearchButtonComponent', 'selector': 'app-search-button'},{'name': 'SearchComponent', 'selector': 'app-search'},{'name': 'SettingItemComponent', 'selector': 'app-setting-item'},{'name': 'SettingsComponent', 'selector': 'app-settings'},{'name': 'SidebarComponent', 'selector': 'app-sidebar'},{'name': 'TemplatePlaygroundComponent', 'selector': 'template-playground-root'},{'name': 'ToastComponent', 'selector': 'app-toast'}];
625+
var COMPONENTS = [{'name': 'AddExpenseComponent', 'selector': 'app-add-expense'},{'name': 'AiComponent', 'selector': 'app-ai'},{'name': 'AnalysisComponent', 'selector': 'app-analysis'},{'name': 'AppComponent', 'selector': 'app-root'},{'name': 'BudgetComponent', 'selector': 'app-budget'},{'name': 'CalendarComponent', 'selector': 'app-calendar'},{'name': 'CategoryDropdownComponent', 'selector': 'app-category-dropdown'},{'name': 'CustomModalComponent', 'selector': 'app-custom-modal'},{'name': 'DownloadComponentComponent', 'selector': 'app-download-component'},{'name': 'ExpenseDetailsModalComponent', 'selector': 'app-expense-details-modal'},{'name': 'ExpenseListComponent', 'selector': 'app-expense-list'},{'name': 'FooterComponent', 'selector': 'app-footer'},{'name': 'FormModelComponent', 'selector': 'app-form-model'},{'name': 'GlobalLoaderComponent', 'selector': 'app-global-loader'},{'name': 'GraphsComponent', 'selector': 'app-graphs'},{'name': 'HamburgerMenuComponent', 'selector': 'app-hamburger-menu'},{'name': 'HomeComponent', 'selector': 'app-home'},{'name': 'InfoIconComponent', 'selector': 'app-info-icon'},{'name': 'InstallAppPopupComponentComponent', 'selector': 'app-install-app-popup-component'},{'name': 'ListExpensesComponent', 'selector': 'app-list-expenses'},{'name': 'MusicComponent', 'selector': 'app-music'},{'name': 'NavbarComponent', 'selector': 'app-navbar'},{'name': 'PieChartComponent', 'selector': 'app-pie-chart'},{'name': 'SearchButtonComponent', 'selector': 'app-search-button'},{'name': 'SearchComponent', 'selector': 'app-search'},{'name': 'SettingItemComponent', 'selector': 'app-setting-item'},{'name': 'SettingsComponent', 'selector': 'app-settings'},{'name': 'SidebarComponent', 'selector': 'app-sidebar'},{'name': 'TemplatePlaygroundComponent', 'selector': 'template-playground-root'},{'name': 'ToastComponent', 'selector': 'app-toast'}];
626626
var DIRECTIVES = [];
627627
var ACTUAL_COMPONENT = {'name': 'AiComponent'};
628628
</script>

0 commit comments

Comments
 (0)