|
|
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 { trigger, transition, style, animate } from '@angular/animations'; |
|
|
import { BrandService } from '../shared/brand.service'; |
|
|
|
|
|
export function nameValidator(control: AbstractControl): ValidationErrors | null { |
|
|
const value = control.value || ''; |
|
|
|
|
|
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; |
|
|
@Output() switchToSignIn = new EventEmitter<void>(); |
|
|
form: FormGroup; |
|
|
private isSubmitting = false; |
|
|
|
|
|
|
|
|
showRoleInfo = false; |
|
|
toggleRoleInfo(ev?: Event) { ev?.stopPropagation(); this.showRoleInfo = !this.showRoleInfo; } |
|
|
hideRoleInfo() { this.showRoleInfo = false; } |
|
|
|
|
|
@Output() close = new EventEmitter<void>(); |
|
|
|
|
|
showPassword = false; |
|
|
showConfirmPassword = false; |
|
|
errorMessage = ''; |
|
|
|
|
|
isSignUpActive = true; |
|
|
public loading = false; |
|
|
submitted = false; |
|
|
|
|
|
|
|
|
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] |
|
|
}, { validators: [this.passwordsMatchValidator] }); |
|
|
|
|
|
|
|
|
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() { |
|
|
|
|
|
alert("Sign-Up button clicked!"); |
|
|
console.log("Sign-Up button clicked!"); |
|
|
|
|
|
this.submitted = true; |
|
|
|
|
|
|
|
|
this.form.markAllAsTouched(); |
|
|
|
|
|
|
|
|
if (this.form.invalid) { |
|
|
console.log("Form is invalid:"); |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
if (!this.form.get('terms')?.value) { |
|
|
this.termsError = 'Please accept Terms & Conditions.'; |
|
|
return; |
|
|
} |
|
|
this.termsError = ''; |
|
|
|
|
|
this.loading = true; |
|
|
try { |
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
this.signUpService.signUp(payload).subscribe( |
|
|
(response) => { |
|
|
this.errorMessage = ''; |
|
|
console.log("Sign-up request sent successfully!"); |
|
|
this.loading = false; |
|
|
|
|
|
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; |
|
|
this.cdr.markForCheck(); |
|
|
setTimeout(() => { |
|
|
this.errorMessage = ''; |
|
|
this.cdr.markForCheck(); |
|
|
}, 3000); |
|
|
} |
|
|
); |
|
|
} catch (error) { |
|
|
console.error("Error occurred during sign-up:", error); |
|
|
this.loading = false; |
|
|
} |
|
|
} |
|
|
|
|
|
navigateHome() { this.router.navigateByUrl('/'); } |
|
|
|
|
|
goToLogin() { |
|
|
this.switchToSignIn.emit(); |
|
|
} |
|
|
|
|
|
closePopup() { |
|
|
try { |
|
|
|
|
|
window.dispatchEvent(new CustomEvent('auth-close')); |
|
|
} catch (e) { |
|
|
|
|
|
} |
|
|
|
|
|
this.close.emit(); |
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
this.cdr.markForCheck(); |
|
|
|
|
|
|
|
|
this.router.navigate(['/home']); |
|
|
} |
|
|
|
|
|
tr(key: string): string { |
|
|
const map: Record<string, string> = { title: 'Create your account', subtitle: 'Join to continue' }; |
|
|
return map[key] || ''; |
|
|
} |
|
|
|
|
|
goToSignIn() { |
|
|
|
|
|
this.switchToSignIn.emit(); |
|
|
} |
|
|
|
|
|
goToSignUp() { |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
function passwordPolicyValidator(control: AbstractControl): ValidationErrors | null { |
|
|
const value = control.value || ''; |
|
|
|
|
|
const policy = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>\/?]).{8,}$/; |
|
|
if (!policy.test(value)) { |
|
|
return { passwordPolicy: true }; |
|
|
} |
|
|
return null; |
|
|
} |
|
|
|