Skip to content

Commit 79addae

Browse files
committed
2 parents 1145c1c + 9f77b78 commit 79addae

102 files changed

Lines changed: 11178 additions & 1633 deletions

File tree

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: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,70 @@ Provides customization and utility options for better personalization:
152152

153153
---
154154

155+
## ☁️ 7. Cloud Backup View
156+
157+
The **Cloud Backup** page provides secure cloud synchronization and backup management for your financial data.
158+
This page can be accessed from **Settings → Cloud Backup** and helps users protect their data against device loss, app reinstall, or accidental deletion.
159+
160+
### 🔐 Key Features
161+
162+
#### ✅ Auto Backup
163+
164+
* Enables **automatic periodic backup** of user data to the cloud.
165+
* Once enabled, the app automatically uploads updated data in the background.
166+
* Users can toggle this option anytime.
167+
168+
#### ☁️ Cloud Synchronization
169+
170+
* Allows users to **manually sync current local data to the cloud**.
171+
* Useful when:
172+
173+
* You want an instant backup.
174+
* You updated important data recently.
175+
* Includes a loading indicator during sync.
176+
177+
#### 🔑 Get Backup Key
178+
179+
* Retrieves a **secure cloud backup key** linked to the user’s stored data.
180+
* This key is required to restore data later.
181+
* Users can:
182+
183+
* View the key securely in a modal.
184+
* Copy it to clipboard for safekeeping.
185+
186+
> ⚠️ Important: Keep this key safe. Without it, data restoration may not be possible.
187+
188+
#### 🔄 Restore From Backup Key
189+
190+
* Allows users to **restore data from the cloud** using their backup key.
191+
* Process includes:
192+
193+
* Entering the backup key.
194+
* Confirming restoration.
195+
* Automatic replacement of current local data with cloud data.
196+
* Includes:
197+
198+
* Data validation before restore.
199+
* Automatic rollback if restoration fails.
200+
201+
---
202+
203+
### 🔒 Data Safety Measures
204+
205+
* Backup key–based authentication ensures secure data access.
206+
* Local data is backed up before restore to prevent accidental loss.
207+
* Invalid or corrupted backup data is rejected automatically.
208+
209+
---
210+
211+
### 📌 Typical Usage Flow
212+
213+
1. Enable **Auto Backup** for continuous protection.
214+
2. Use **Sync to Cloud** before switching devices.
215+
3. Save your **Backup Key** securely.
216+
4. Use **Restore from Backup Key** when reinstalling or changing devices.
217+
218+
155219
## 📱 Mobile-First Design
156220

157221
* The application is specifically designed for **mobile devices**.

documentation/components/AddExpenseComponent.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1628,7 +1628,7 @@ <h3 id="inputs">
16281628
<script src="../js/libs/deep-iterator.js"></script>
16291629
<script>
16301630
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>'
1631-
var COMPONENTS = [{'name': 'AddExpenseComponent', 'selector': 'app-add-expense'},{'name': 'AiComponent', 'selector': 'app-ai'},{'name': 'AppComponent', 'selector': 'app-root'},{'name': 'CalendarComponent', 'selector': 'app-calendar'},{'name': 'CategoryDropdownComponent', 'selector': 'app-category-dropdown'},{'name': 'DownloadComponentComponent', 'selector': 'app-download-component'},{'name': 'ExpenseDetailsModalComponent', 'selector': 'app-expense-details-modal'},{'name': 'ExpenseListComponent', 'selector': 'app-expense-list'},{'name': 'ExpenseWiseComponent', 'selector': 'app-expense-wise'},{'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': 'HelpDashboard', 'selector': 'app-help-dashboard'},{'name': 'HomeComponent', 'selector': 'app-home'},{'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': 'PlaylistMusicComponent', 'selector': 'app-playlist-music'},{'name': 'SalaryComponent', 'selector': 'app-salary'},{'name': 'SearchButtonComponent', 'selector': 'app-search-button'},{'name': 'SearchMusicComponent', 'selector': 'app-search-music'},{'name': 'SettingItemComponent', 'selector': 'app-setting-item'},{'name': 'SettingsComponent', 'selector': 'app-settings'},{'name': 'SidebarComponent', 'selector': 'app-sidebar'},{'name': 'SplashScreenComponent', 'selector': 'app-splash-screen'},{'name': 'TemplatePlaygroundComponent', 'selector': 'template-playground-root'},{'name': 'ToastComponent', 'selector': 'app-toast'}];
1631+
var COMPONENTS = [{'name': 'AddExpenseComponent', 'selector': 'app-add-expense'},{'name': 'AiComponent', 'selector': 'app-ai'},{'name': 'AppComponent', 'selector': 'app-root'},{'name': 'CalendarComponent', 'selector': 'app-calendar'},{'name': 'CategoryDropdownComponent', 'selector': 'app-category-dropdown'},{'name': 'CloudBackupComponent', 'selector': 'app-cloud-backup'},{'name': 'DownloadComponentComponent', 'selector': 'app-download-component'},{'name': 'ExpenseDetailsModalComponent', 'selector': 'app-expense-details-modal'},{'name': 'ExpenseListComponent', 'selector': 'app-expense-list'},{'name': 'ExpenseWiseComponent', 'selector': 'app-expense-wise'},{'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': 'HelpDashboard', 'selector': 'app-help-dashboard'},{'name': 'HomeComponent', 'selector': 'app-home'},{'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': 'PlaylistMusicComponent', 'selector': 'app-playlist-music'},{'name': 'SalaryComponent', 'selector': 'app-salary'},{'name': 'SearchButtonComponent', 'selector': 'app-search-button'},{'name': 'SearchMusicComponent', 'selector': 'app-search-music'},{'name': 'SettingItemComponent', 'selector': 'app-setting-item'},{'name': 'SettingsComponent', 'selector': 'app-settings'},{'name': 'SidebarComponent', 'selector': 'app-sidebar'},{'name': 'SplashScreenComponent', 'selector': 'app-splash-screen'},{'name': 'TemplatePlaygroundComponent', 'selector': 'template-playground-root'},{'name': 'ToastComponent', 'selector': 'app-toast'}];
16321632
var DIRECTIVES = [];
16331633
var ACTUAL_COMPONENT = {'name': 'AddExpenseComponent'};
16341634
</script>

0 commit comments

Comments
 (0)