Enhance staff application flow with email verification checks, refined error handling, and improved user feedback in frontend and backend.
This commit is contained in:
parent
cdbf862ecf
commit
311d77fcb2
|
|
@ -38,45 +38,37 @@ public class ApplicationController implements ApplicationsApi {
|
|||
public ResponseEntity<FormResponseDto> submitStaffApplication(StaffApplicationDto staffApplicationDto) {
|
||||
UUID userUuid = AuthenticatedUuid.getAuthenticatedUserUuid();
|
||||
|
||||
String email = staffApplicationDto.getEmail() == null ? null : staffApplicationDto.getEmail().toLowerCase();
|
||||
Optional<EmailVerification> optionalEmail = fetchEmailVerification(userUuid, email);
|
||||
if (optionalEmail.isEmpty() || !optionalEmail.get().verified()) {
|
||||
log.warn("User {} attempted to submit an application without a verified email {}", userUuid, email);
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
// Map and persist application
|
||||
StaffApplication application = staffApplicationDataMapper.map(userUuid, staffApplicationDto);
|
||||
saveApplication(application);
|
||||
|
||||
Optional<EmailVerification> optionalEmail = fetchEmailVerification(userUuid, application.email());
|
||||
boolean verified = optionalEmail.map(EmailVerification::verified).orElse(false);
|
||||
|
||||
boolean success = true;
|
||||
if (verified) {
|
||||
// Send mail first; only if sent, send to Discord, then mark as sent
|
||||
boolean mailSent = false;
|
||||
try {
|
||||
mailSent = staffApplicationMail.sendApplicationEmail(application);
|
||||
} catch (Exception e) {
|
||||
log.error("Error while sending staff application email for {}", application.id(), e);
|
||||
success = false;
|
||||
try {
|
||||
if (!staffApplicationMail.sendApplicationEmail(application)) {
|
||||
log.warn("Failed to send staff application email for {}", application.id());
|
||||
return ResponseEntity.internalServerError().build();
|
||||
}
|
||||
|
||||
if (mailSent) {
|
||||
try {
|
||||
staffApplicationDiscord.sendApplicationToDiscord(application);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send staff application {} to Discord", application.id(), e);
|
||||
success = false;
|
||||
}
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
markAsSent(application.id());
|
||||
}
|
||||
}
|
||||
|
||||
if (verified && !success) {
|
||||
} catch (Exception e) {
|
||||
log.error("Error while sending staff application email for {}", application.id(), e);
|
||||
return ResponseEntity.internalServerError().build();
|
||||
}
|
||||
|
||||
FormResponseDto response = buildResponse(application, verified);
|
||||
return ResponseEntity.status(201).body(response);
|
||||
try {
|
||||
staffApplicationDiscord.sendApplicationToDiscord(application);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to send staff application {} to Discord", application.id(), e);
|
||||
return ResponseEntity.internalServerError().build();
|
||||
}
|
||||
markAsSent(application.id());
|
||||
|
||||
FormResponseDto response = buildResponse(application);
|
||||
return ResponseEntity.status(200).body(response);
|
||||
}
|
||||
|
||||
private void saveApplication(StaffApplication application) {
|
||||
|
|
@ -110,10 +102,8 @@ public class ApplicationController implements ApplicationsApi {
|
|||
.runQuery(sqlSession -> sqlSession.getMapper(StaffApplicationMapper.class).markAsSent(applicationId));
|
||||
}
|
||||
|
||||
private FormResponseDto buildResponse(StaffApplication application, boolean verified) {
|
||||
String message = verified
|
||||
? "Your staff application has been submitted. You will be notified when it has been reviewed."
|
||||
: "Application created. Please verify your email to complete submission.";
|
||||
private FormResponseDto buildResponse(StaffApplication application) {
|
||||
String message = "Your staff application has been submitted. You will be notified when it has been reviewed.";
|
||||
return new FormResponseDto(
|
||||
application.id().toString(),
|
||||
message,
|
||||
|
|
|
|||
|
|
@ -67,7 +67,14 @@
|
|||
<div class="valid-email">
|
||||
<ng-container matSuffix>
|
||||
<mat-icon>check</mat-icon>
|
||||
<span>You have validated your email previously, and can continue to the next page!</span>
|
||||
<span>You have validated your email previously.</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="invalid-email">
|
||||
<ng-container matSuffix>
|
||||
<mat-icon>close</mat-icon>
|
||||
<span>You have not used this email address before. Before going to the next page you will be asked to verify it.</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -107,7 +114,8 @@
|
|||
<!-- PC Requirements -->
|
||||
<div class="checkbox-field">
|
||||
<mat-checkbox formControlName="meetsRequirements">
|
||||
I confirm that I meet the PC requirements (able to record video at 30fps 720p or higher, and able to talk in voice chat)
|
||||
I confirm that I meet the PC requirements (able to record video at 30fps 720p or higher, and able
|
||||
to talk in voice chat)
|
||||
</mat-checkbox>
|
||||
@if (form.controls.meetsRequirements.invalid && form.controls.meetsRequirements.touched) {
|
||||
<mat-error class="checkbox-error">
|
||||
|
|
@ -172,7 +180,8 @@
|
|||
<label class="field-label">Available days for moderating:</label>
|
||||
<div class="days-container">
|
||||
@for (day of availableDays; track day) {
|
||||
<div class="day-chip" [class.selected]="form.controls.availableDays.value.includes(day)" (click)="toggleDay(day)">
|
||||
<div class="day-chip" [class.selected]="form.controls.availableDays.value.includes(day)"
|
||||
(click)="toggleDay(day)">
|
||||
{{ day }}
|
||||
</div>
|
||||
}
|
||||
|
|
@ -188,9 +197,9 @@
|
|||
<mat-form-field appearance="fill" style="width: 100%;">
|
||||
<mat-label>Available times (Your timezone: {{ userTimezone }})</mat-label>
|
||||
<textarea matInput
|
||||
formControlName="availableTimes"
|
||||
placeholder="e.g., 6PM-10PM weekdays, 2PM-8PM weekends"
|
||||
rows="2"></textarea>
|
||||
formControlName="availableTimes"
|
||||
placeholder="e.g., 6PM-10PM weekdays, 2PM-8PM weekends"
|
||||
rows="2"></textarea>
|
||||
@if (form.controls.availableTimes.invalid && form.controls.availableTimes.touched) {
|
||||
<mat-error>
|
||||
Available times are required
|
||||
|
|
@ -215,9 +224,9 @@
|
|||
<mat-form-field appearance="fill" style="width: 100%;">
|
||||
<mat-label>Previous experience (here or in other relevant places)</mat-label>
|
||||
<textarea matInput
|
||||
formControlName="previousExperience"
|
||||
placeholder="Describe your previous experience"
|
||||
rows="4"></textarea>
|
||||
formControlName="previousExperience"
|
||||
placeholder="Describe your previous experience"
|
||||
rows="4"></textarea>
|
||||
@if (form.controls.previousExperience.invalid && form.controls.previousExperience.touched) {
|
||||
<mat-error>
|
||||
@if (form.controls.previousExperience.errors?.['required']) {
|
||||
|
|
@ -233,9 +242,9 @@
|
|||
<mat-form-field appearance="fill" style="width: 100%;">
|
||||
<mat-label>Experience with plugins that players use on our server</mat-label>
|
||||
<textarea matInput
|
||||
formControlName="pluginExperience"
|
||||
placeholder="Describe your experience with our server plugins"
|
||||
rows="4"></textarea>
|
||||
formControlName="pluginExperience"
|
||||
placeholder="Describe your experience with our server plugins"
|
||||
rows="4"></textarea>
|
||||
@if (form.controls.pluginExperience.invalid && form.controls.pluginExperience.touched) {
|
||||
<mat-error>
|
||||
@if (form.controls.pluginExperience.errors?.['required']) {
|
||||
|
|
@ -251,9 +260,9 @@
|
|||
<mat-form-field appearance="fill" style="width: 100%;">
|
||||
<mat-label>What do you believe the expectations of a moderator are?</mat-label>
|
||||
<textarea matInput
|
||||
formControlName="moderatorExpectations"
|
||||
placeholder="Describe what you think a moderator should do"
|
||||
rows="4"></textarea>
|
||||
formControlName="moderatorExpectations"
|
||||
placeholder="Describe what you think a moderator should do"
|
||||
rows="4"></textarea>
|
||||
@if (form.controls.moderatorExpectations.invalid && form.controls.moderatorExpectations.touched) {
|
||||
<mat-error>
|
||||
@if (form.controls.moderatorExpectations.errors?.['required']) {
|
||||
|
|
@ -269,9 +278,9 @@
|
|||
<mat-form-field appearance="fill" style="width: 100%;">
|
||||
<mat-label>Additional Information (Optional)</mat-label>
|
||||
<textarea matInput
|
||||
formControlName="additionalInfo"
|
||||
placeholder="Any additional information you'd like to share"
|
||||
rows="4"></textarea>
|
||||
formControlName="additionalInfo"
|
||||
placeholder="Any additional information you'd like to share"
|
||||
rows="4"></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<button mat-raised-button (click)="onSubmit()"
|
||||
|
|
|
|||
|
|
@ -99,6 +99,16 @@ main {
|
|||
background-color: rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
.invalid-email {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #af4c4c;
|
||||
margin: 10px 0;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
background-color: rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
.valid-email mat-icon {
|
||||
color: #4CAF50;
|
||||
margin-right: 10px;
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ export class StaffApplicationComponent implements OnInit, OnDestroy, AfterViewIn
|
|||
}),
|
||||
availableTimes: new FormControl('', {
|
||||
nonNullable: true,
|
||||
validators: [Validators.required, Validators.maxLength(1000)]
|
||||
validators: [Validators.required, Validators.maxLength(900)]
|
||||
}),
|
||||
previousExperience: new FormControl('', {
|
||||
nonNullable: true,
|
||||
|
|
@ -234,10 +234,11 @@ export class StaffApplicationComponent implements OnInit, OnDestroy, AfterViewIn
|
|||
const staffApplication: StaffApplication = this.mapToStaffApplication(this.form.getRawValue());
|
||||
|
||||
this.staffApplicationService.submitStaffApplication(staffApplication).subscribe(result => {
|
||||
//TODO route to mail page
|
||||
// Navigate to the sent page
|
||||
if (!result.verified_mail) {
|
||||
throw new Error('Submitted a form with an e-mail that was not verified.');
|
||||
}
|
||||
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.'}
|
||||
state: {message: result.message}
|
||||
}).then();
|
||||
})
|
||||
}
|
||||
|
|
@ -321,7 +322,7 @@ export class StaffApplicationComponent implements OnInit, OnDestroy, AfterViewIn
|
|||
joinDate: joinDateString,
|
||||
weeklyPlaytime: Number(formData.weeklyPlaytime),
|
||||
availableDays: formData.availableDays,
|
||||
availableTimes: formData.availableTimes,
|
||||
availableTimes: `Timezone: ${this.userTimezone}\nAvailable Times:${formData.availableTimes}`,
|
||||
previousExperience: formData.previousExperience,
|
||||
pluginExperience: formData.pluginExperience,
|
||||
moderatorExpectations: formData.moderatorExpectations,
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ components:
|
|||
description: Preferred pronouns of the applicant
|
||||
joinDate:
|
||||
type: string
|
||||
maxLength: 256
|
||||
format: date
|
||||
description: Date when the applicant joined the service
|
||||
weeklyPlaytime:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user