import { Component, Output, EventEmitter, ChangeDetectorRef, Input, OnInit, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators, AbstractControl, ValidationErrors } from '@angular/forms'; import { Router, RouterLink } from '@angular/router'; import { SignUpService } from './sign-up.service'; // Import the SignUpService import { trigger, transition, style, animate } from '@angular/animations'; import { BrandService } from '../shared/brand.service'; // <-- Add this import export function nameValidator(control: AbstractControl): ValidationErrors | null { const value = control.value || ''; // Only allow alphabets and spaces, min2 chars if (!/^[A-Za-z ]{2,}$/.test(value)) { return { invalidName: true }; } return null; } @Component({ selector: 'app-sign-up', standalone: true, imports: [CommonModule, ReactiveFormsModule, RouterLink], templateUrl: './sign-up.component.html', styleUrls: ['./sign-up.component.css'], animations: [ trigger('fadeInOut', [ transition(':enter', [ style({ opacity:0 }), animate('600ms', style({ opacity:1 })) ]), transition(':leave', [ animate('600ms', style({ opacity:0 })) ]) ]) ] }) export class SignUpComponent implements OnInit, OnDestroy { @Input() embedded = false; // when true, render only inner panel (for embedding in auth-card) @Output() switchToSignIn = new EventEmitter(); form: FormGroup; private isSubmitting = false; // Added: state & handlers for role info popover used in template showRoleInfo = false; toggleRoleInfo(ev?: Event) { ev?.stopPropagation(); this.showRoleInfo = !this.showRoleInfo; } hideRoleInfo() { this.showRoleInfo = false; } @Output() close = new EventEmitter(); showPassword = false; showConfirmPassword = false; errorMessage = ''; isSignUpActive = true; // ← Added state for sign-up panel activation public loading = false; // Used to disable the button during sign-up submitted = false; // Track form submission status // Added: terms & conditions error handling termsError: string = ''; facts: string[] = [ '🎯 Master grammar with adaptive quizzes.', '📖 Build comprehension with AI reading practice.', '🗣️ Improve listening and pronunciation with feedback.' ]; currentFact: string = this.facts[0]; private factIndex =0; private factInterval: any; showInfo = false; constructor( private fb: FormBuilder, private router: Router, private signUpService: SignUpService, private cdr: ChangeDetectorRef, public brand: BrandService ) { this.form = this.fb.group({ name: ['', [Validators.required, Validators.minLength(2), nameValidator]], lastName: ['', [Validators.required, Validators.minLength(2), nameValidator]], email: ['', [ Validators.required, Validators.pattern(/(^[^@]+@[^@]+\.[^@]+$)|(^\+?\d[\d\-\s]{8,14}\d$)/) ]], password: ['', [Validators.required, Validators.minLength(8), passwordPolicyValidator]], confirmPassword: ['', [Validators.required]], role: ['', [Validators.required]], terms: [false, Validators.requiredTrue] // Added terms control with requiredTrue validator }, { validators: [this.passwordsMatchValidator] }); // Close popover when clicking anywhere in document (capture phase not needed here) document.addEventListener('click', () => this.hideRoleInfo()); } ngOnInit() { this.startFactRotation(); } ngOnDestroy() { if (this.factInterval) { clearInterval(this.factInterval); } } startFactRotation() { this.factInterval = setInterval(() => { this.factIndex = (this.factIndex +1) % this.facts.length; this.currentFact = this.facts[this.factIndex]; },5000); } control(path: string): AbstractControl | null { return this.form.get(path); } controlHasError(path: string, error?: string): boolean { const c = this.control(path); if (!c) return false; return error ? !!(c.touched && c.errors?.[error]) : !!(c.touched && c.invalid); } showPwdMismatch(): boolean { const pw = this.control('password'); const cpw = this.control('confirmPassword'); const groupMismatch = this.form.errors?.['passwordMismatch']; return !!(pw && cpw && (cpw.touched || pw.touched) && groupMismatch); } passwordsMatchValidator(group: AbstractControl) { const pw = group.get('password')?.value; const cpw = group.get('confirmPassword')?.value; return pw && cpw && pw === cpw ? null : { passwordMismatch: true }; } togglePasswordVisibility() { this.showPassword = !this.showPassword; } toggleConfirmPasswordVisibility() { this.showConfirmPassword = !this.showConfirmPassword; } submit() { // Confirm button click alert("Sign-Up button clicked!"); console.log("Sign-Up button clicked!"); this.submitted = true; // Track the form submission attempt // Mark all form controls as touched to trigger validation this.form.markAllAsTouched(); // Check if the form is invalid if (this.form.invalid) { console.log("Form is invalid:"); // Log individual control states for debugging Object.keys(this.form.controls).forEach(controlName => { const control = this.form.get(controlName); if (control?.invalid) { console.log(`${controlName} is invalid. Errors:`, control?.errors); } }); return; } // Check terms & conditions acceptance if (!this.form.get('terms')?.value) { this.termsError = 'Please accept Terms & Conditions.'; return; } this.termsError = ''; this.loading = true; // Set loading to true when starting submission try { // Prepare the payload to send to the backend const payload = { name: this.control('name')?.value, lastName: this.control('lastName')?.value, email: this.control('email')?.value, password: this.control('password')?.value, role: this.control('role')?.value }; console.log("Form data before submission:", this.form.value); console.log("Payload to send:", payload); // Make the HTTP request this.signUpService.signUp(payload).subscribe( (response) => { this.errorMessage = ''; console.log("Sign-up request sent successfully!"); this.loading = false; // Reset loading on success // On success: if embedded inside SignIn card, flip back to sign-in; else go to /login if (this.embedded) { this.switchToSignIn.emit(); } else { this.router.navigate(['/login']); } }, (error) => { if (error && error.status === 400) { this.errorMessage = 'This email or username is already registered.'; } else { this.errorMessage = 'An error occurred. Please try again.'; } this.loading = false; // Reset loading on error this.cdr.markForCheck(); setTimeout(() => { this.errorMessage = ''; this.cdr.markForCheck(); }, 3000); } ); } catch (error) { console.error("Error occurred during sign-up:", error); // Log any errors from the API call this.loading = false; // Reset loading on exception } } navigateHome() { this.router.navigateByUrl('/'); } goToLogin() { this.switchToSignIn.emit(); // ← Emit event instead of router navigation } closePopup() { try { // dispatch a global event so parent or other listeners always can close modals window.dispatchEvent(new CustomEvent('auth-close')); } catch (e) { // ignore } this.close.emit(); // Defensive: remove modal/backdrop if parent didn't hide them try { const modal = document.querySelector('.modal'); if (modal && modal.parentElement) modal.parentElement.removeChild(modal); const backdrop = document.querySelector('.modal-backdrop'); if (backdrop && backdrop.parentElement) backdrop.parentElement.removeChild(backdrop); } catch (e) { console.warn('Failed to remove modal/backdrop DOM elements', e); } // Ensure change detection updates this.cdr.markForCheck(); // Navigate to home after closing this.router.navigate(['/home']); } tr(key: string): string { const map: Record = { title: 'Create your account', subtitle: 'Join to continue' }; return map[key] || ''; } goToSignIn() { // Emit to parent when embedded so the card can slide back this.switchToSignIn.emit(); } goToSignUp() { // no-op when embedded } } function passwordPolicyValidator(control: AbstractControl): ValidationErrors | null { const value = control.value || ''; // Policy: min 8 chars, 1 uppercase, 1 lowercase, 1 number, 1 special char const policy = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>\/?]).{8,}$/; if (!policy.test(value)) { return { passwordPolicy: true }; } return null; }