349 lines
11 KiB
TypeScript
349 lines
11 KiB
TypeScript
|
|
import {
|
||
|
|
AfterViewInit,
|
||
|
|
Component,
|
||
|
|
computed,
|
||
|
|
ElementRef,
|
||
|
|
inject,
|
||
|
|
OnDestroy,
|
||
|
|
OnInit,
|
||
|
|
Renderer2,
|
||
|
|
signal
|
||
|
|
} from '@angular/core';
|
||
|
|
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||
|
|
import {ApplicationsService, EmailEntry, MailService, StaffApplication} from '@api';
|
||
|
|
import {HeaderComponent} from '@header/header.component';
|
||
|
|
import {NgOptimizedImage} from '@angular/common';
|
||
|
|
import {MatButtonModule} from '@angular/material/button';
|
||
|
|
import {MatIconModule} from '@angular/material/icon';
|
||
|
|
import {AuthService} from '@services/auth.service';
|
||
|
|
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
||
|
|
import {MatFormFieldModule} from '@angular/material/form-field';
|
||
|
|
import {MatSelectModule} from '@angular/material/select';
|
||
|
|
import {MatInputModule} from '@angular/material/input';
|
||
|
|
import {MatDialog} from '@angular/material/dialog';
|
||
|
|
import {VerifyMailDialogComponent} from '@pages/forms/verify-mail-dialog/verify-mail-dialog.component';
|
||
|
|
import {Router} from '@angular/router';
|
||
|
|
import {MatCheckboxModule} from '@angular/material/checkbox';
|
||
|
|
import {MatDatepickerModule} from '@angular/material/datepicker';
|
||
|
|
import {MatNativeDateModule} from '@angular/material/core';
|
||
|
|
import {MatChipsModule} from '@angular/material/chips';
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
selector: 'app-staff-application',
|
||
|
|
imports: [
|
||
|
|
HeaderComponent,
|
||
|
|
NgOptimizedImage,
|
||
|
|
MatButtonModule,
|
||
|
|
MatIconModule,
|
||
|
|
MatProgressSpinnerModule,
|
||
|
|
MatFormFieldModule,
|
||
|
|
MatSelectModule,
|
||
|
|
MatInputModule,
|
||
|
|
ReactiveFormsModule,
|
||
|
|
MatCheckboxModule,
|
||
|
|
MatDatepickerModule,
|
||
|
|
MatNativeDateModule,
|
||
|
|
MatChipsModule
|
||
|
|
],
|
||
|
|
templateUrl: './staff-application.component.html',
|
||
|
|
styleUrl: './staff-application.component.scss'
|
||
|
|
})
|
||
|
|
export class StaffApplicationComponent implements OnInit, OnDestroy, AfterViewInit {
|
||
|
|
|
||
|
|
private mailService = inject(MailService);
|
||
|
|
public authService = inject(AuthService);
|
||
|
|
public staffApplicationService = inject(ApplicationsService)
|
||
|
|
private resizeObserver: ResizeObserver | null = null;
|
||
|
|
private boundHandleResize: any;
|
||
|
|
|
||
|
|
protected form: FormGroup<StaffApplicationForm>;
|
||
|
|
private emails = signal<EmailEntry[]>([]);
|
||
|
|
protected verifiedEmails = computed(() => this.emails()
|
||
|
|
.filter(email => email.verified)
|
||
|
|
.map(email => email.email.toLowerCase()));
|
||
|
|
protected emailIsValid = signal<boolean>(false);
|
||
|
|
protected dialog = inject(MatDialog);
|
||
|
|
protected availableDays: string[] = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||
|
|
protected selectedDays: string[] = [];
|
||
|
|
protected userTimezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||
|
|
|
||
|
|
|
||
|
|
constructor(
|
||
|
|
private elementRef: ElementRef,
|
||
|
|
private renderer: Renderer2
|
||
|
|
) {
|
||
|
|
const staffApplication: StaffApplicationForm = {
|
||
|
|
email: new FormControl('', {
|
||
|
|
nonNullable: true,
|
||
|
|
validators: [Validators.required, Validators.email, Validators.maxLength(320)]
|
||
|
|
}),
|
||
|
|
age: new FormControl('', {
|
||
|
|
nonNullable: true,
|
||
|
|
validators: [Validators.required, Validators.min(13), Validators.pattern('^[0-9]*$')]
|
||
|
|
}),
|
||
|
|
discordUsername: new FormControl('', {
|
||
|
|
nonNullable: true,
|
||
|
|
validators: [Validators.required, Validators.maxLength(32)]
|
||
|
|
}),
|
||
|
|
meetsRequirements: new FormControl(false, {
|
||
|
|
nonNullable: true,
|
||
|
|
validators: [Validators.requiredTrue]
|
||
|
|
}),
|
||
|
|
pronouns: new FormControl('', {
|
||
|
|
nonNullable: true,
|
||
|
|
validators: [Validators.maxLength(32)]
|
||
|
|
}),
|
||
|
|
joinDate: new FormControl('', {
|
||
|
|
nonNullable: true,
|
||
|
|
validators: [Validators.required]
|
||
|
|
}),
|
||
|
|
weeklyPlaytime: new FormControl('', {
|
||
|
|
nonNullable: true,
|
||
|
|
validators: [Validators.required, Validators.min(1)]
|
||
|
|
}),
|
||
|
|
availableDays: new FormControl([], {
|
||
|
|
nonNullable: true,
|
||
|
|
validators: [Validators.required]
|
||
|
|
}),
|
||
|
|
availableTimes: new FormControl('', {
|
||
|
|
nonNullable: true,
|
||
|
|
validators: [Validators.required, Validators.maxLength(1000)]
|
||
|
|
}),
|
||
|
|
previousExperience: new FormControl('', {
|
||
|
|
nonNullable: true,
|
||
|
|
validators: [Validators.required, Validators.minLength(10), Validators.maxLength(4000)]
|
||
|
|
}),
|
||
|
|
pluginExperience: new FormControl('', {
|
||
|
|
nonNullable: true,
|
||
|
|
validators: [Validators.required, Validators.minLength(10), Validators.maxLength(4000)]
|
||
|
|
}),
|
||
|
|
moderatorExpectations: new FormControl('', {
|
||
|
|
nonNullable: true,
|
||
|
|
validators: [Validators.required, Validators.minLength(10), Validators.maxLength(4000)]
|
||
|
|
}),
|
||
|
|
additionalInfo: new FormControl('', {
|
||
|
|
nonNullable: true,
|
||
|
|
validators: [Validators.maxLength(4000)]
|
||
|
|
})
|
||
|
|
}
|
||
|
|
this.form = new FormGroup(staffApplication);
|
||
|
|
|
||
|
|
this.mailService.getUserEmails().subscribe(emails => {
|
||
|
|
this.emails.set(emails);
|
||
|
|
});
|
||
|
|
|
||
|
|
this.form.valueChanges.subscribe(() => {
|
||
|
|
if (this.verifiedEmails().includes(this.form.getRawValue().email.toLowerCase())) {
|
||
|
|
this.emailIsValid.set(true);
|
||
|
|
} else {
|
||
|
|
this.emailIsValid.set(false);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
computed(() => {
|
||
|
|
if (this.verifiedEmails().length > 0) {
|
||
|
|
this.form.get('email')?.setValue(this.verifiedEmails()[0]);
|
||
|
|
this.emailIsValid.set(true);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
ngOnInit() {
|
||
|
|
const uuid = this.authService.getUuid();
|
||
|
|
if (uuid === null) {
|
||
|
|
alert('Error retrieving token, please relog on the website and try again')
|
||
|
|
throw new Error('JWT subject is null, are you logged in?');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ngAfterViewInit() {
|
||
|
|
this.setupResizeObserver();
|
||
|
|
this.updateContainerHeight();
|
||
|
|
|
||
|
|
this.boundHandleResize = this.handleResize.bind(this);
|
||
|
|
window.addEventListener('resize', this.boundHandleResize);
|
||
|
|
|
||
|
|
setTimeout(() => this.updateContainerHeight(), 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
ngOnDestroy() {
|
||
|
|
if (this.resizeObserver) {
|
||
|
|
this.resizeObserver.disconnect();
|
||
|
|
this.resizeObserver = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.boundHandleResize) {
|
||
|
|
window.removeEventListener('resize', this.boundHandleResize);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private handleResize() {
|
||
|
|
this.updateContainerHeight();
|
||
|
|
}
|
||
|
|
|
||
|
|
private setupResizeObserver() {
|
||
|
|
this.resizeObserver = new ResizeObserver(() => {
|
||
|
|
this.updateContainerHeight();
|
||
|
|
});
|
||
|
|
|
||
|
|
const headerElement = document.querySelector('app-header');
|
||
|
|
if (headerElement) {
|
||
|
|
this.resizeObserver.observe(headerElement);
|
||
|
|
}
|
||
|
|
|
||
|
|
const footerElement = document.querySelector('footer');
|
||
|
|
if (footerElement) {
|
||
|
|
this.resizeObserver.observe(footerElement);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private updateContainerHeight() {
|
||
|
|
const headerElement = document.querySelector('app-header');
|
||
|
|
const footerElement = document.querySelector('footer');
|
||
|
|
|
||
|
|
const container = this.elementRef.nativeElement.querySelector('.staff-application-container');
|
||
|
|
|
||
|
|
if (headerElement && footerElement && container) {
|
||
|
|
const headerHeight = headerElement.getBoundingClientRect().height;
|
||
|
|
const footerHeight = footerElement.getBoundingClientRect().height;
|
||
|
|
|
||
|
|
const calculatedHeight = `calc(100vh - ${headerHeight}px - ${footerHeight}px)`;
|
||
|
|
this.renderer.setStyle(container, 'min-height', calculatedHeight);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public onSubmit() {
|
||
|
|
if (this.form === undefined) {
|
||
|
|
console.error('Form is undefined');
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if (this.form.valid) {
|
||
|
|
this.sendForm()
|
||
|
|
} else {
|
||
|
|
// Mark all fields as touched to show validation errors
|
||
|
|
Object.keys(this.form.controls).forEach(field => {
|
||
|
|
const control = this.form.get(field);
|
||
|
|
control?.markAsTouched();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private router = inject(Router)
|
||
|
|
|
||
|
|
private sendForm() {
|
||
|
|
const staffApplication: StaffApplication = this.mapToStaffApplication(this.form.getRawValue());
|
||
|
|
|
||
|
|
this.staffApplicationService.submitStaffApplication(staffApplication).subscribe(result => {
|
||
|
|
//TODO route to mail page
|
||
|
|
// Navigate to the sent page
|
||
|
|
this.router.navigate(['/forms/sent'], {
|
||
|
|
state: {message: 'Your staff application has been submitted successfully. We will review your application and get back to you soon.'}
|
||
|
|
}).then();
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
public currentPageIndex: number = 0;
|
||
|
|
public totalPages: number[] = [0, 1, 2, 3, 4];
|
||
|
|
|
||
|
|
public goToPage(pageIndex: number): void {
|
||
|
|
if (pageIndex >= 0 && pageIndex < this.totalPages.length) {
|
||
|
|
this.currentPageIndex = pageIndex;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public previousPage() {
|
||
|
|
this.goToPage(this.currentPageIndex - 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
public nextPage() {
|
||
|
|
this.goToPage(this.currentPageIndex + 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
public isFirstPage(): boolean {
|
||
|
|
return this.currentPageIndex === 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
public isLastPage(): boolean {
|
||
|
|
return this.currentPageIndex === this.totalPages.length - 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected validateMailOrNextPage() {
|
||
|
|
if (this.emailIsValid()) {
|
||
|
|
this.nextPage();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const dialogRef = this.dialog.open(VerifyMailDialogComponent, {
|
||
|
|
data: {email: this.form.getRawValue().email},
|
||
|
|
});
|
||
|
|
dialogRef.afterClosed().subscribe(result => {
|
||
|
|
if (result === true) {
|
||
|
|
this.emailIsValid.set(true);
|
||
|
|
this.nextPage();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
toggleDay(day: string) {
|
||
|
|
const availableDaysControl = this.form.get('availableDays');
|
||
|
|
const currentDays = [...(availableDaysControl?.value || [])];
|
||
|
|
|
||
|
|
if (currentDays.includes(day)) {
|
||
|
|
const index = currentDays.indexOf(day);
|
||
|
|
currentDays.splice(index, 1);
|
||
|
|
} else {
|
||
|
|
currentDays.push(day);
|
||
|
|
}
|
||
|
|
|
||
|
|
availableDaysControl?.setValue(currentDays);
|
||
|
|
}
|
||
|
|
|
||
|
|
private mapToStaffApplication(formData: any): StaffApplication {
|
||
|
|
let joinDateString: string;
|
||
|
|
|
||
|
|
if (formData.joinDate instanceof Date) {
|
||
|
|
joinDateString = formData.joinDate.toISOString();
|
||
|
|
} else if (typeof formData.joinDate === 'string' && formData.joinDate.trim() !== '') {
|
||
|
|
const parsedDate = new Date(formData.joinDate);
|
||
|
|
if (isNaN(parsedDate.getTime())) {
|
||
|
|
throw new Error('Invalid date string');
|
||
|
|
}
|
||
|
|
joinDateString = parsedDate.toISOString();
|
||
|
|
} else {
|
||
|
|
throw new Error('Invalid date string');
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
email: formData.email,
|
||
|
|
age: Number(formData.age),
|
||
|
|
discordUsername: formData.discordUsername,
|
||
|
|
meetsRequirements: formData.meetsRequirements,
|
||
|
|
pronouns: formData.pronouns || '',
|
||
|
|
joinDate: joinDateString,
|
||
|
|
weeklyPlaytime: Number(formData.weeklyPlaytime),
|
||
|
|
availableDays: formData.availableDays,
|
||
|
|
availableTimes: formData.availableTimes,
|
||
|
|
previousExperience: formData.previousExperience,
|
||
|
|
pluginExperience: formData.pluginExperience,
|
||
|
|
moderatorExpectations: formData.moderatorExpectations,
|
||
|
|
additionalInfo: formData.additionalInfo || ''
|
||
|
|
};
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
interface StaffApplicationForm {
|
||
|
|
email: FormControl<string>;
|
||
|
|
age: FormControl<string>;
|
||
|
|
discordUsername: FormControl<string>;
|
||
|
|
meetsRequirements: FormControl<boolean>;
|
||
|
|
pronouns: FormControl<string>;
|
||
|
|
joinDate: FormControl<string>;
|
||
|
|
weeklyPlaytime: FormControl<string>;
|
||
|
|
availableDays: FormControl<string[]>;
|
||
|
|
availableTimes: FormControl<string>;
|
||
|
|
previousExperience: FormControl<string>;
|
||
|
|
pluginExperience: FormControl<string>;
|
||
|
|
moderatorExpectations: FormControl<string>;
|
||
|
|
additionalInfo: FormControl<string>;
|
||
|
|
}
|