added error validation for first create event step

This commit is contained in:
Igor Hrenowitsch Propisnov 2024-08-22 11:07:25 +02:00
parent 1475b13360
commit 2805e57e60
5 changed files with 128 additions and 17 deletions

View File

@ -5,6 +5,7 @@ import {
signal,
WritableSignal,
effect,
ViewChild,
} from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@ -20,6 +21,7 @@ import { TicketsStepComponent } from './steps/tickets-step.component';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CreateEventComponent {
@ViewChild(BasicStepComponent) public basicStep!: BasicStepComponent;
public currentStep: WritableSignal<number> = signal(0);
public readonly steps: string[] = ['Basic', 'Tickets', 'Review'];
public form!: FormGroup;
@ -94,6 +96,7 @@ export class CreateEventComponent {
this.hasAttemptedNextStep.set(false);
} else {
this.hasAttemptedNextStep.set(true);
this.markCurrentStepAsTouched();
}
}
}
@ -106,13 +109,20 @@ export class CreateEventComponent {
public getNextButtonClass(): string {
return this.hasAttemptedNextStep() && !this.isCurrentStepValid()
? 'btn btn-outline btn-warning'
? 'btn btn-outline btn-error'
: 'btn btn-primary btn-outline';
}
public getNextButtonText(): string {
return this.hasAttemptedNextStep() && !this.isCurrentStepValid()
? 'Required Fields Missing'
? 'Required Fields Need Attention'
: `Next to ${this.steps[this.currentStep() + 1]}`;
}
private markCurrentStepAsTouched(): void {
if (this.currentStep() === 0 && this.basicStep) {
this.basicStep.markAllAsTouched();
}
// step 1 and 2 missing
}
}

View File

@ -14,11 +14,13 @@
class="input input-bordered w-full" />
<div class="label">
<span class="label-text-alt"></span>
<span class="label-text-alt"></span>
<span class="label-text-alt text-error">
{{ getErrorMessage('eventTitle') }}
</span>
</div>
</label>
<div class="form-control w-full">
<div class="form-control w-full mb-4">
<app-dropdown
formControlName="eventLocation"
label="Event Location"
@ -28,6 +30,8 @@
newItemText="Create new Location"
emptyStateText="No matching Locations found"
[items]="items"
[required]="true"
[errorMessage]="getErrorMessage('eventLocation')"
(submitNewItems)="onDropdownSubmit()"></app-dropdown>
</div>
@ -45,7 +49,9 @@
class="input input-bordered w-full" />
<div class="label">
<span class="label-text-alt"></span>
<span class="label-text-alt"></span>
<span class="label-text-alt text-error">
{{ getErrorMessage('eventDate') }}
</span>
</div>
</label>
@ -62,7 +68,9 @@
class="input input-bordered w-full" />
<div class="label">
<span class="label-text-alt"></span>
<span class="label-text-alt"></span>
<span class="label-text-alt text-error">
{{ getErrorMessage('eventTime') }}
</span>
</div>
</label>
</div>

View File

@ -6,7 +6,12 @@ import {
ViewChild,
input,
} from '@angular/core';
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import {
FormGroup,
FormsModule,
ReactiveFormsModule,
ValidationErrors,
} from '@angular/forms';
import { DropdownComponent } from '../../../../shared/components/dropdown/dropdown.component';
import {
@ -31,6 +36,8 @@ import {
export class BasicStepComponent {
@ViewChild(LocationDialogComponent)
public locationModal!: LocationDialogComponent;
@ViewChild(DropdownComponent)
public dropdownComponent!: DropdownComponent;
public items: string[] = ['Nachtigal Köln'];
public form: InputSignal<FormGroup> = input.required<FormGroup>();
@ -53,6 +60,71 @@ export class BasicStepComponent {
//TODO: save location
}
public markAllAsTouched(): void {
Object.values(this.form().controls).forEach((control) => {
control.markAsTouched();
});
if (this.dropdownComponent) {
this.dropdownComponent.markAsTouched();
}
}
public getErrorMessage(controlName: string): string {
const control = this.form().get(controlName);
if (control && control.touched && control.errors) {
return this.getErrorMessageFromValidationErrors(
control.errors,
controlName
);
}
return '';
}
private getErrorMessageFromValidationErrors(
errors: ValidationErrors,
controlName: string
): string {
switch (controlName) {
case 'eventTitle':
if (errors['required']) {
return 'Event title is required to uniquely identify your event in our system and for attendees.';
}
if (errors['minlength']) {
return `Please provide a more descriptive title (minimum ${errors['minlength'].requiredLength} characters) to improve discoverability.`;
}
break;
case 'eventLocation':
if (errors['required']) {
return 'Location is crucial for attendees and for our logistics planning. Please select or add a venue.';
}
if (errors['invalidOption']) {
return 'Please select a valid location from the list or add a new one to ensure accurate event information.';
}
break;
case 'eventDate':
if (errors['required']) {
return 'Event date is essential for scheduling and marketing. Please select a date for your event.';
}
// You might add a custom validator for dates in the past
if (errors['pastDate']) {
return 'Please select a future date. Our platform is designed for upcoming events.';
}
break;
case 'eventTime':
if (errors['required']) {
return 'Start time helps attendees plan their schedule. Please specify when your event begins.';
}
break;
}
// Generic messages for any other errors
if (errors['required']) {
return 'This information is required to ensure a complete and accurate event listing.';
}
return 'Please check this field to ensure all event details are correct and complete.';
}
private openLocationModal(): void {
this.locationModal.openModal();
}

View File

@ -114,6 +114,12 @@
</ul>
}
</div>
<div class="label">
<span class="label-text-alt"></span>
<span class="label-text-alt text-error">
{{ errorMessage() }}
</span>
</div>
<div class="label">
<span class="label-text-alt">{{ hintBottomLeft() }}</span>
<span class="label-text-alt">{{ hintBottomRight() }}</span>

View File

@ -41,6 +41,7 @@ import {
})
export class DropdownComponent implements ControlValueAccessor, Validator {
public label: InputSignal<string> = input.required<string>();
public errorMessage: InputSignal<string> = input.required<string>();
public placeholder: InputSignal<string> = input.required<string>();
public items: InputSignal<string[]> = input.required<string[]>();
public emptyStateText: InputSignal<string> = input<string>('');
@ -59,6 +60,7 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
public itemSelected: OutputEmitterRef<string> = output<string>();
public submitNewItems: OutputEmitterRef<boolean> = output<boolean>();
public itemEdit: OutputEmitterRef<string> = output<string>();
public touched: WritableSignal<boolean> = signal(false);
private isMouseInDropdown: WritableSignal<boolean> = signal<boolean>(false);
public constructor(private readonly elementRef: ElementRef) {}
@ -78,6 +80,7 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
this.searchTerm.set('');
this.selectedItem.set(null);
}
this.touched.set(false);
}
public registerOnChange(fn: (value: string) => void): void {
@ -85,7 +88,10 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
}
public registerOnTouched(fn: () => void): void {
this.onTouched = fn;
this.onTouched = (): void => {
this.touched.set(true);
fn();
};
}
public validate(control: AbstractControl): ValidationErrors | null {
@ -148,6 +154,7 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
}
public getInputClass(): string {
if (this.touched()) {
if (this.selectedItem()) {
return 'input-success';
} else if (
@ -158,9 +165,17 @@ export class DropdownComponent implements ControlValueAccessor, Validator {
} else if (this.required() && this.searchTerm().trim() === '') {
return 'input-error';
}
}
return '';
}
public markAsTouched(): void {
if (!this.touched()) {
this.touched.set(true);
this.onTouched();
}
}
public editItem(item: string, event: MouseEvent): void {
event.preventDefault();
event.stopPropagation();