add changes

This commit is contained in:
AD2025
2025-11-14 21:48:47 +02:00
parent 6f23890407
commit 37b4d565b1
72 changed files with 17104 additions and 246 deletions

View File

@@ -0,0 +1,276 @@
import { Component, OnInit, inject, DestroyRef } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatCardModule } from '@angular/material/card';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatDividerModule } from '@angular/material/divider';
import { AdminService } from '../../../core/services/admin.service';
import { GuestSettings } from '../../../core/models/admin.model';
/**
* GuestSettingsEditComponent
*
* Form component for editing guest access settings.
* Allows administrators to configure guest user limitations and features.
*
* Features:
* - Reactive form with validation
* - Real-time validation errors
* - Settings preview before save
* - Form reset functionality
* - Success/error handling
* - Navigation back to view mode
*/
@Component({
selector: 'app-guest-settings-edit',
standalone: true,
imports: [
CommonModule,
ReactiveFormsModule,
MatCardModule,
MatButtonModule,
MatIconModule,
MatInputModule,
MatFormFieldModule,
MatSlideToggleModule,
MatProgressSpinnerModule,
MatTooltipModule,
MatDividerModule
],
templateUrl: './guest-settings-edit.component.html',
styleUrl: './guest-settings-edit.component.scss'
})
export class GuestSettingsEditComponent implements OnInit {
private readonly adminService = inject(AdminService);
private readonly router = inject(Router);
private readonly fb = inject(FormBuilder);
private readonly destroyRef = inject(DestroyRef);
// Service signals
readonly settings = this.adminService.guestSettingsState;
readonly isLoading = this.adminService.isLoadingSettings;
readonly error = this.adminService.settingsError;
// Form
settingsForm!: FormGroup;
isSubmitting = false;
originalSettings: GuestSettings | null = null;
ngOnInit(): void {
this.initializeForm();
this.loadSettings();
}
/**
* Initialize the form with validation
*/
private initializeForm(): void {
this.settingsForm = this.fb.group({
guestAccessEnabled: [false],
maxQuizzesPerDay: [3, [Validators.required, Validators.min(1), Validators.max(100)]],
maxQuestionsPerQuiz: [10, [Validators.required, Validators.min(1), Validators.max(50)]],
sessionExpiryHours: [24, [Validators.required, Validators.min(1), Validators.max(168)]],
upgradePromptMessage: [
'You\'ve reached your quiz limit. Sign up for unlimited access!',
[Validators.required, Validators.minLength(10), Validators.maxLength(500)]
]
});
}
/**
* Load existing settings and populate form
*/
private loadSettings(): void {
// If settings already loaded, use them
if (this.settings()) {
this.populateForm(this.settings()!);
return;
}
// Otherwise fetch settings
this.adminService.getGuestSettings()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(settings => {
this.populateForm(settings);
});
}
/**
* Populate form with existing settings
*/
private populateForm(settings: GuestSettings): void {
this.originalSettings = settings;
this.settingsForm.patchValue({
guestAccessEnabled: settings.guestAccessEnabled,
maxQuizzesPerDay: settings.maxQuizzesPerDay,
maxQuestionsPerQuiz: settings.maxQuestionsPerQuiz,
sessionExpiryHours: settings.sessionExpiryHours,
upgradePromptMessage: settings.upgradePromptMessage
});
}
/**
* Submit form and update settings
*/
onSubmit(): void {
if (this.settingsForm.invalid || this.isSubmitting) {
this.settingsForm.markAllAsTouched();
return;
}
this.isSubmitting = true;
const formData = this.settingsForm.value;
this.adminService.updateGuestSettings(formData)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: () => {
this.isSubmitting = false;
// Navigate back to view page after short delay
setTimeout(() => {
this.router.navigate(['/admin/guest-settings']);
}, 1500);
},
error: () => {
this.isSubmitting = false;
}
});
}
/**
* Cancel editing and return to view page
*/
onCancel(): void {
if (this.hasUnsavedChanges()) {
if (confirm('You have unsaved changes. Are you sure you want to cancel?')) {
this.router.navigate(['/admin/guest-settings']);
}
} else {
this.router.navigate(['/admin/guest-settings']);
}
}
/**
* Reset form to original values
*/
onReset(): void {
if (this.originalSettings) {
this.populateForm(this.originalSettings);
}
}
/**
* Check if form has unsaved changes
*/
hasUnsavedChanges(): boolean {
if (!this.originalSettings) return false;
const formValue = this.settingsForm.value;
return (
formValue.guestAccessEnabled !== this.originalSettings.guestAccessEnabled ||
formValue.maxQuizzesPerDay !== this.originalSettings.maxQuizzesPerDay ||
formValue.maxQuestionsPerQuiz !== this.originalSettings.maxQuestionsPerQuiz ||
formValue.sessionExpiryHours !== this.originalSettings.sessionExpiryHours ||
formValue.upgradePromptMessage !== this.originalSettings.upgradePromptMessage
);
}
/**
* Get error message for a form field
*/
getErrorMessage(fieldName: string): string {
const field = this.settingsForm.get(fieldName);
if (!field?.errors || !field.touched) return '';
if (field.errors['required']) return 'This field is required';
if (field.errors['min']) return `Minimum value is ${field.errors['min'].min}`;
if (field.errors['max']) return `Maximum value is ${field.errors['max'].max}`;
if (field.errors['minlength']) return `Minimum length is ${field.errors['minlength'].requiredLength} characters`;
if (field.errors['maxlength']) return `Maximum length is ${field.errors['maxlength'].requiredLength} characters`;
return 'Invalid value';
}
/**
* Check if a field has an error
*/
hasError(fieldName: string): boolean {
const field = this.settingsForm.get(fieldName);
return !!(field?.invalid && field?.touched);
}
/**
* Get preview of changes
*/
getChangesPreview(): Array<{label: string, old: any, new: any}> {
if (!this.originalSettings || !this.hasUnsavedChanges()) return [];
const changes: Array<{label: string, old: any, new: any}> = [];
const formValue = this.settingsForm.value;
if (formValue.guestAccessEnabled !== this.originalSettings.guestAccessEnabled) {
changes.push({
label: 'Guest Access',
old: this.originalSettings.guestAccessEnabled ? 'Enabled' : 'Disabled',
new: formValue.guestAccessEnabled ? 'Enabled' : 'Disabled'
});
}
if (formValue.maxQuizzesPerDay !== this.originalSettings.maxQuizzesPerDay) {
changes.push({
label: 'Max Quizzes Per Day',
old: this.originalSettings.maxQuizzesPerDay,
new: formValue.maxQuizzesPerDay
});
}
if (formValue.maxQuestionsPerQuiz !== this.originalSettings.maxQuestionsPerQuiz) {
changes.push({
label: 'Max Questions Per Quiz',
old: this.originalSettings.maxQuestionsPerQuiz,
new: formValue.maxQuestionsPerQuiz
});
}
if (formValue.sessionExpiryHours !== this.originalSettings.sessionExpiryHours) {
changes.push({
label: 'Session Expiry Hours',
old: this.originalSettings.sessionExpiryHours,
new: formValue.sessionExpiryHours
});
}
if (formValue.upgradePromptMessage !== this.originalSettings.upgradePromptMessage) {
changes.push({
label: 'Upgrade Prompt Message',
old: this.originalSettings.upgradePromptMessage,
new: formValue.upgradePromptMessage
});
}
return changes;
}
/**
* Format expiry time for display
*/
formatExpiryTime(hours: number): string {
if (hours < 24) {
return `${hours} hour${hours !== 1 ? 's' : ''}`;
}
const days = Math.floor(hours / 24);
const remainingHours = hours % 24;
if (remainingHours === 0) {
return `${days} day${days !== 1 ? 's' : ''}`;
}
return `${days} day${days !== 1 ? 's' : ''} and ${remainingHours} hour${remainingHours !== 1 ? 's' : ''}`;
}
}