Skip to content

Commit 45db1a7

Browse files
committed
update
1 parent 5d37336 commit 45db1a7

12 files changed

Lines changed: 377 additions & 13 deletions

File tree

src/app/component/settings-components/cloud-backup/cloud-backup.component.css

Whitespace-only changes.
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
<div class="p-4 sm:p-6 section">
2+
<div class="transition-colors duration-300 font-sans">
3+
<div class="space-y-6">
4+
5+
<!-- Section: Auto Backup -->
6+
<div
7+
class="flex items-center justify-between bg-[var(--color-surface)] p-4 rounded-xl border border-[var(--color-surface-500)]">
8+
<div class="pr-2">
9+
<h2 class="text-lg font-semibold text-[var(--color-text)]">Auto Backup</h2>
10+
<p class="text-xs sm:text-sm text-[var(--color-text-600)] mt-1">Automatically backup your
11+
data periodically</p>
12+
</div>
13+
<!-- Custom Toggle -->
14+
<label class="relative inline-flex items-center cursor-pointer">
15+
<input type="checkbox" [checked]="autoBackupEnabled()" (change)="toggleAutoBackup()"
16+
class="sr-only peer">
17+
<div
18+
class="w-10 h-6 bg-[var(--color-gray-600)] rounded-full peer peer-checked:bg-[var(--theme-color)] transition-all">
19+
</div>
20+
<span
21+
class="absolute left-1 top-1 w-4 h-4 bg-white rounded-full transition-all peer-checked:translate-x-4"></span>
22+
</label>
23+
</div>
24+
25+
<!-- Section: Cloud Sync -->
26+
<div
27+
class="flex items-center justify-between bg-[var(--color-surface)] p-4 rounded-xl border border-[var(--color-surface-500)]">
28+
<div class="pr-2">
29+
<h2 class="text-lg font-semibold text-[var(--color-text)]">Cloud Synchronization</h2>
30+
<p class="text-xs sm:text-sm text-[var(--color-text-600)] mt-1">Save current data to cloud
31+
</p>
32+
</div>
33+
<button (click)="syncToCloud()" [disabled]="isSyncing()"
34+
class="w-[50px] h-[50px] flex items-center justify-center bg-[var(--color-surface-500)] hover:bg-[var(--color-surface-600)] text-[var(--color-text)] font-medium rounded-full transition-all disabled:opacity-50 disabled:cursor-not-allowed border border-[var(--color-surface-600)] shrink-0">
35+
@if (isSyncing()) {
36+
<svg class="animate-spin h-5 w-5 text-[var(--theme-color)]"
37+
xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" fill="none"
38+
viewBox="0 0 24 24">
39+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
40+
</circle>
41+
<path class="opacity-75" fill="currentColor"
42+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
43+
</path>
44+
</svg>
45+
} @else {
46+
<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" width="20" height="20"
47+
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
48+
stroke-linejoin="round">
49+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
50+
<polyline points="17 8 12 3 7 8" />
51+
<line x1="12" y1="3" x2="12" y2="15" />
52+
</svg>
53+
}
54+
</button>
55+
</div>
56+
57+
<!-- Section: Generate Backup Key Button -->
58+
<div class="flex items-center justify-between bg-[var(--color-surface)] p-4 rounded-xl border border-[var(--color-surface-500)]">
59+
<div class="pr-2">
60+
<h2 class="text-lg font-semibold text-[var(--color-text)]">Generate Backup Key</h2>
61+
<p class="text-xs sm:text-sm text-[var(--color-text-600)] mt-1">Create a secure key to
62+
manually export and save your data.</p>
63+
</div>
64+
<button (click)="openBackupModal()"
65+
class="flex items-center gap-2 px-4 py-2 bg-[var(--theme-color)] hover:bg-[var(--color-accent-600)] text-white font-medium rounded-xl transition-colors shadow-sm shrink-0 whitespace-nowrap">
66+
<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" width="18" height="18"
67+
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
68+
stroke-linejoin="round">
69+
<path d="M2 18v3c0 .6.4 1 1 1h4v-3h3v-3h2l1.4-1.4a6.5 6.5 0 1 0-4-4Z" />
70+
<circle cx="16.5" cy="7.5" r=".5" fill="currentColor" />
71+
</svg>
72+
<span class="hidden sm:inline">Get Key</span>
73+
</button>
74+
</div>
75+
76+
<!-- Section: Restore Data Button -->
77+
<div class="flex items-center justify-between bg-[var(--color-surface)] p-4 rounded-xl border border-[var(--color-surface-500)]">
78+
<div class="pr-2">
79+
<h2 class="text-lg font-semibold text-[var(--color-text)]">Restore from Key</h2>
80+
<p class="text-xs sm:text-sm text-[var(--color-text-600)] mt-1">Restore your data from a
81+
previously saved state using a key.</p>
82+
</div>
83+
<button (click)="openRestoreModal()"
84+
class="flex items-center gap-2 px-4 py-2 bg-[var(--color-surface-500)] hover:bg-[var(--color-surface-600)] border border-[var(--color-surface-600)] text-[var(--color-text)] font-medium rounded-xl transition-colors shrink-0 whitespace-nowrap">
85+
<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" width="18" height="18"
86+
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
87+
stroke-linejoin="round">
88+
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
89+
<path d="M3 3v5h5" />
90+
</svg>
91+
<span class="hidden sm:inline">Restore</span>
92+
</button>
93+
</div>
94+
95+
</div>
96+
</div>
97+
98+
99+
<!-- MODAL: Generate Backup Key -->
100+
@if (isBackupModalOpen()) {
101+
<div
102+
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4 transition-all duration-300">
103+
<div
104+
class="bg-[var(--color-surface-100)] border border-[var(--color-surface-500)] rounded-2xl shadow-2xl w-full max-w-md overflow-hidden transform scale-100 opacity-100 transition-all">
105+
<div
106+
class="p-5 border-b border-[var(--color-surface-500)] bg-[var(--color-surface)] flex justify-between items-center">
107+
<h3 class="text-lg font-bold text-[var(--color-text)]">Your Backup Key</h3>
108+
<button (click)="closeBackupModal()"
109+
class="text-[var(--color-text-600)] hover:text-[var(--color-text)] transition-colors p-1 rounded-full hover:bg-[var(--color-surface-500)]">
110+
<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" width="20" height="20"
111+
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
112+
stroke-linejoin="round">
113+
<path d="M18 6 6 18" />
114+
<path d="m6 6 12 12" />
115+
</svg>
116+
</button>
117+
</div>
118+
<div class="p-6">
119+
<p class="text-sm text-[var(--color-text-600)] mb-4">Copy this key and save it securely. You will
120+
need it to restore your data.</p>
121+
<div class="flex items-center gap-2">
122+
<div
123+
class="flex-1 bg-[var(--input-bg)] border border-[var(--input-border)] text-[var(--input-text)] p-3 rounded-xl font-mono text-sm break-all shadow-inner">
124+
{{ backupKey() }}
125+
</div>
126+
<button (click)="copyBackupKey()"
127+
class="p-3 bg-[var(--theme-color)] hover:bg-[var(--color-accent-600)] text-white rounded-xl transition-colors shrink-0 shadow-sm"
128+
title="Copy to clipboard">
129+
<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" width="20" height="20"
130+
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
131+
stroke-linecap="round" stroke-linejoin="round">
132+
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
133+
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
134+
</svg>
135+
</button>
136+
</div>
137+
<button (click)="closeBackupModal()"
138+
class="w-full mt-6 py-2.5 bg-[var(--color-surface-500)] hover:bg-[var(--color-surface-600)] text-[var(--color-text)] font-medium rounded-xl transition-colors">
139+
Done
140+
</button>
141+
</div>
142+
</div>
143+
</div>
144+
}
145+
146+
<!-- MODAL: Restore Data -->
147+
@if (isRestoreModalOpen()) {
148+
<div
149+
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4 transition-all duration-300">
150+
<div
151+
class="bg-[var(--color-surface-100)] border border-[var(--color-surface-500)] rounded-2xl shadow-2xl w-full max-w-md overflow-hidden transform scale-100 opacity-100 transition-all">
152+
<div
153+
class="p-5 border-b border-[var(--color-surface-500)] bg-[var(--color-surface)] flex justify-between items-center">
154+
<h3 class="text-lg font-bold text-[var(--color-text)]">Restore Data</h3>
155+
<button (click)="closeRestoreModal()"
156+
class="text-[var(--color-text-600)] hover:text-[var(--color-text)] transition-colors p-1 rounded-full hover:bg-[var(--color-surface-500)]"
157+
[disabled]="isRestoring()">
158+
<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" width="20" height="20"
159+
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
160+
stroke-linejoin="round">
161+
<path d="M18 6 6 18" />
162+
<path d="m6 6 12 12" />
163+
</svg>
164+
</button>
165+
</div>
166+
<div class="p-6">
167+
<p class="text-sm text-[var(--color-text-600)] mb-4">Paste your generated backup key below to
168+
restore your data.</p>
169+
<div class="space-y-4">
170+
<input type="text" [value]="restoreInput()" (input)="onRestoreInput($event)"
171+
placeholder="Paste backup key here..."
172+
class="w-full px-4 py-3 bg-[var(--input-bg)] border border-[var(--input-border)] text-[var(--input-text)] rounded-xl focus:outline-none focus:ring-2 focus:ring-[var(--theme-color)] focus:border-transparent transition-all"
173+
[disabled]="isRestoring()">
174+
<button (click)="restoreData()" [disabled]="!restoreInput().trim() || isRestoring()"
175+
class="w-full flex items-center justify-center gap-2 px-6 py-3 bg-[var(--theme-color)] hover:bg-[var(--color-accent-600)] disabled:opacity-50 disabled:cursor-not-allowed text-white font-medium rounded-xl transition-all shadow-sm">
176+
@if (isRestoring()) {
177+
<svg class="animate-spin h-5 w-5 text-white"
178+
xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" fill="none"
179+
viewBox="0 0 24 24">
180+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
181+
</circle>
182+
<path class="opacity-75" fill="currentColor"
183+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
184+
</path>
185+
</svg>
186+
Restoring...
187+
} @else {
188+
<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" width="18" height="18"
189+
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
190+
stroke-linecap="round" stroke-linejoin="round">
191+
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
192+
<path d="M3 3v5h5" />
193+
</svg>
194+
Restore Data
195+
}
196+
</button>
197+
</div>
198+
</div>
199+
</div>
200+
</div>
201+
}
202+
203+
</div>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { CloudBackupComponent } from './cloud-backup.component';
4+
5+
describe('CloudBackupComponent', () => {
6+
let component: CloudBackupComponent;
7+
let fixture: ComponentFixture<CloudBackupComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [CloudBackupComponent]
12+
})
13+
.compileComponents();
14+
15+
fixture = TestBed.createComponent(CloudBackupComponent);
16+
component = fixture.componentInstance;
17+
fixture.detectChanges();
18+
});
19+
20+
it('should create', () => {
21+
expect(component).toBeTruthy();
22+
});
23+
});
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { Component, ChangeDetectionStrategy, signal } from '@angular/core';
2+
import { ToastService } from '../../../service/toast/toast.service';
3+
import { PostApiService } from '../../../service/backend-api/post/post-api.service';
4+
import { UserService } from '../../../service/localStorage/user.service';
5+
6+
@Component({
7+
selector: 'app-cloud-backup',
8+
standalone: true,
9+
imports: [],
10+
changeDetection: ChangeDetectionStrategy.OnPush,
11+
templateUrl: './cloud-backup.component.html',
12+
styleUrls: ['./cloud-backup.component.css']
13+
})
14+
export class CloudBackupComponent {
15+
16+
constructor(private toastService: ToastService, private postApiService: PostApiService, private userService: UserService) {
17+
this.autoBackupEnabled.set(this.userService.getValue<boolean>('is_backup_enable') || false);
18+
}
19+
20+
// Backup States
21+
autoBackupEnabled = signal<boolean>(false);
22+
isSyncing = signal<boolean>(false);
23+
backupKey = signal<string | null>(null);
24+
25+
// Modal States
26+
isBackupModalOpen = signal<boolean>(false);
27+
isRestoreModalOpen = signal<boolean>(false);
28+
29+
// Restore States
30+
restoreInput = signal<string>('');
31+
isRestoring = signal<boolean>(false);
32+
33+
// Toggle Auto Backup
34+
toggleAutoBackup() {
35+
this.autoBackupEnabled.update(val => !val);
36+
this.userService.update('is_backup_enable', this.autoBackupEnabled());
37+
this.toastService.show(
38+
this.autoBackupEnabled() ? 'Auto backup enabled' : 'Auto backup disabled',
39+
'success'
40+
);
41+
}
42+
43+
// Cloud Sync Simulation
44+
syncToCloud() {
45+
this.isSyncing.set(true);
46+
47+
// Simulate API call
48+
this.postApiService.postUserData(true);
49+
50+
this.isSyncing.set(false);
51+
}
52+
53+
// Modal Controls for Backup Key
54+
openBackupModal() {
55+
this.postApiService.postUserData();
56+
this.isBackupModalOpen.set(true);
57+
}
58+
59+
closeBackupModal() {
60+
this.isBackupModalOpen.set(false);
61+
}
62+
63+
// Generate Backup Key
64+
generateBackupKey() {
65+
// Generate a random UUID-like string for the key
66+
const newKey = crypto.randomUUID ? crypto.randomUUID() : 'bkp-8f2a-4c91-b3d5-e7f620a1';
67+
this.backupKey.set(newKey);
68+
}
69+
70+
// Copy Key to Clipboard
71+
copyBackupKey() {
72+
const key = this.backupKey();
73+
if (key) {
74+
if (navigator.clipboard && window.isSecureContext) {
75+
navigator.clipboard.writeText(key).then(() => {
76+
this.toastService.show('The key is copied', 'success');
77+
});
78+
} else {
79+
const textArea = document.createElement("textarea");
80+
textArea.value = key;
81+
document.body.appendChild(textArea);
82+
textArea.focus();
83+
textArea.select();
84+
try {
85+
this.toastService.show('The key is copied', 'success');
86+
} catch (err) {
87+
this.toastService.show('Failed to copy key', 'error');
88+
}
89+
document.body.removeChild(textArea);
90+
}
91+
}
92+
}
93+
94+
// Modal Controls for Restore
95+
openRestoreModal() {
96+
this.isRestoreModalOpen.set(true);
97+
}
98+
99+
closeRestoreModal() {
100+
if (this.isRestoring()) return;
101+
this.isRestoreModalOpen.set(false);
102+
this.restoreInput.set(''); // Clear input on close
103+
}
104+
105+
// Update Restore Input without ngModel
106+
onRestoreInput(event: Event) {
107+
const target = event.target as HTMLInputElement;
108+
this.restoreInput.set(target.value);
109+
}
110+
111+
// Restore Data Simulation
112+
async restoreData() {
113+
const key = this.restoreInput().trim();
114+
if (!key) return;
115+
116+
this.isRestoring.set(true);
117+
118+
// Simulate API Network Request delay
119+
await new Promise(resolve => setTimeout(resolve, 2500));
120+
121+
this.isRestoring.set(false);
122+
123+
// Validate dummy key (simulate an error logic)
124+
if (key.length < 10) {
125+
this.toastService.show('Invalid backup key provided.', 'error');
126+
} else {
127+
this.toastService.show('Data successfully restored from backup!', 'success');
128+
this.closeRestoreModal(); // Automatically close the modal on success
129+
}
130+
}
131+
}

src/app/features/expense-wise/expense-wise.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<app-help-dashboard *ngSwitchCase="'help'"></app-help-dashboard>
1717
<app-home *ngSwitchDefault></app-home>
1818
<app-salary *ngSwitchCase="'salary'"></app-salary>
19+
<app-cloud-backup *ngSwitchCase="'cloudBackup'"></app-cloud-backup>
1920
</ng-container>
2021
</main>
2122
</div>

src/app/features/expense-wise/expense-wise.component.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ import { HomeComponent } from '../../features/home/home.component';
1313
import { CalendarComponent } from '../../features/calendar/calendar.component';
1414
import { AiComponent } from '../../features/ai/ai.component';
1515
import { SalaryComponent } from '../salary/salary.component';
16-
16+
import { CloudBackupComponent } from '../../component/settings-components/cloud-backup/cloud-backup.component';
1717
import { SectionService } from '../../service/section/section.service';
1818
import { ScreenTypeService } from '../../service/screen-type/screen-type.service';
1919
import { NativeAppServiceService } from '../../service/native-app/native-app-service.service';
2020
import { HelpDashboard } from '../help-dashboard/help-dashboard';
21+
2122
/**
2223
* Root component of the application.
2324
* Manages global state, mobile view detection, section tracking,
@@ -28,7 +29,7 @@ import { HelpDashboard } from '../help-dashboard/help-dashboard';
2829
standalone: true,
2930
imports: [
3031
NavbarComponent, SidebarComponent, FooterComponent, ToastComponent, CommonModule, AddExpenseComponent,
31-
SettingsComponent, ListExpensesComponent, HomeComponent, CalendarComponent, AiComponent, HelpDashboard, SalaryComponent
32+
SettingsComponent, ListExpensesComponent, HomeComponent, CalendarComponent, AiComponent, HelpDashboard, SalaryComponent, CloudBackupComponent
3233
],
3334
templateUrl: './expense-wise.component.html',
3435
styleUrl: './expense-wise.component.css'

0 commit comments

Comments
 (0)