py-learn / src /app /sign-up /sign-up.component.ts
Anupriya
added footer and logo
6dc3ffe
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<void>();
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<void>();
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<string, string> = { 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;
}