Overview
The NewQuestComponent provides a comprehensive form for creating and editing quests. It includes validation, category selection, difficulty settings, and integrates with the quest management system.
Features
- Create & Edit Modes: Single component handles both creating new quests and editing existing ones
- Form Validation: Real-time validation for required fields
- Category Selection: Visual category picker using horizontal cards
- Difficulty Levels: Interactive flame icons for setting difficulty (1-3)
- Tag Management: Add and remove custom tags
- Loading States: Shows loading spinner during save operations
- Toast Notifications: User feedback for success/error states
Component Definition
@Component({
selector: 'app-new-quest',
templateUrl: './new-quest.component.html',
standalone: true,
imports: [
CommonModule,
FormsModule,
CardCategoryHorizontallyComponent,
IonicModule
]
})
export class NewQuestComponent implements OnInit {
@Input() questToEdit?: Quest;
questData: Quest = {
id: crypto.randomUUID(),
title: '',
description: '',
category: 'frontend',
difficulty: 2,
xpReward: 250,
status: 'pending'
};
isEditing: boolean = false;
editingQuestId: string | null = null;
}
<ion-content class="quest-content flex-container">
<ion-header class="realm-header">
<ion-toolbar>
<ion-title>{{ isEditing ? 'EDITAR MISIÓN' : 'FORJAR MISIÓN' }}</ion-title>
</ion-toolbar>
</ion-header>
<div class="card-wrapper">
<ion-card class="realm-card neon-border realm-blue">
<ion-card-content>
<!-- Quest Name -->
<div class="form-field mb-6">
<ion-label class="field-label">Nombre de la Misión</ion-label>
<input type="text" [(ngModel)]="questData.title" />
</div>
<!-- Category Selection -->
<app-card-category-horizontally
[multiple]="false"
[selectedCategoryName]="getCategoryDisplayName()"
(categorySelected)="onCategorySelected($event)">
</app-card-category-horizontally>
<!-- Difficulty -->
<div class="difficulty-selector">
<ion-icon name="flame"
[class.active]="questData.difficulty >= 1"
(click)="setDifficulty(1)">
</ion-icon>
</div>
<!-- Action Buttons -->
<button class="forge-btn" (click)="createQuest()" [disabled]="!isFormValid()">
{{ isEditing ? 'ACTUALIZAR' : 'FORJAR' }}
</button>
</ion-card-content>
</ion-card>
</div>
</ion-content>
Quest object to edit. When provided, the form switches to edit mode and pre-fills with quest data.
The form manages a Quest object with the following structure:
questData: Quest = {
id: crypto.randomUUID(),
title: '', // Required
description: '',
category: 'frontend', // Required
kingdom: 'frontend',
icon: 'terminal',
colorClass: 'color-blue',
glowClass: 'neon-border',
dueDate: '',
difficulty: 2, // Required (1-3)
xpReward: 250, // Required
reward: '+250 XP',
badges: [],
tags: ['TRABAJO PROFUNDO'],
createdAt: new Date(),
status: 'pending'
};
Key Methods
createQuest()
Creates a new quest or updates an existing one.
async createQuest() {
if (!this.isFormValid()) {
await this.showToast('Por favor completa todos los campos requeridos', 'warning');
return;
}
const loading = await this.loadingController.create({
message: this.isEditing ? 'Actualizando misión...' : 'Forjando misión...',
spinner: 'circles'
});
await loading.present();
try {
const questData = {
...this.questData,
reward: `+${this.questData.xpReward} XP`,
tags: this.questData.tags.length > 0 ? this.questData.tags : ['TRABAJO PROFUNDO']
};
if (this.isEditing && this.editingQuestId) {
savedQuest = this.questService.updateQuest(this.editingQuestId, questData);
await this.showToast('Misión actualizada con éxito', 'success');
} else {
savedQuest = this.questService.createQuest(questData);
await this.showToast('¡Misión forjada con éxito!', 'success');
}
this.questSharedService.triggerRefresh();
this.resetForm();
this.goToQuestList();
} catch (error) {
await this.showToast('Error al procesar la misión', 'danger');
} finally {
await loading.dismiss();
}
}
Validates the form before submission.
isFormValid(): boolean {
return !!(
this.questData.title?.trim() &&
this.questData.category &&
this.questData.difficulty > 0 &&
this.questData.xpReward > 0
);
}
onCategorySelected()
Handles category selection from the horizontal category component.
onCategorySelected(categories: Category[]) {
const category = categories[0];
if (!category) {
this.questData.category = "";
this.questData.colorClass = 'realm-green';
this.questData.icon = 'terminal';
return;
}
this.questData.category = category.name;
this.questData.colorClass = category.colorClass || 'realm-green';
this.questData.icon = category.icon || 'terminal';
}
setDifficulty()
Sets the quest difficulty level (1-3).
setDifficulty(level: number) {
this.questData.difficulty = level;
}
addTag()
Adds a custom tag to the quest.
addTag(event: Event) {
const input = event.target as HTMLInputElement;
const tag = input.value?.trim();
if (tag && !this.questData.tags.includes(tag)) {
this.questData.tags.push(tag);
this.newTag = '';
}
}
removeTag()
Removes a tag from the quest.
removeTag(index: number) {
if (index >= 0 && index < this.questData.tags.length) {
this.questData.tags.splice(index, 1);
}
}
The form validates the following required fields:
- Title: Must not be empty
- Category: Must be selected
- Difficulty: Must be greater than 0 (1-3)
- XP Reward: Must be greater than 0
The “Forjar” button is disabled until all required fields are valid.
Edit Mode
The component can load an existing quest for editing in two ways:
<app-new-quest [questToEdit]="selectedQuest"></app-new-quest>
2. Via Route Parameters
// Route: /quests/edit/:id
private loadQuestForEditing(questId: string) {
const quest = this.questService.getQuestById(questId);
if (quest) {
this.isEditing = true;
this.editingQuestId = questId;
this.questData = { ...quest };
}
}
Category Mappings
The component maintains mappings for category display:
categoryTextMap = {
'design': 'Diseño',
'frontend': 'Desarrollo',
'other': 'Marketing',
'devops': 'DevOps',
'mobile': 'Mobile',
'ai': 'AI',
'data': 'Data',
'security': 'Security',
'softskills': 'Soft Skills'
};
Difficulty Mapping
difficultyMap = {
1: 'Fácil',
2: 'Medio',
3: 'Difícil'
};
User Feedback
The component provides feedback through:
Loading States
const loading = await this.loadingController.create({
message: 'Forjando misión...',
spinner: 'circles'
});
Toast Notifications
private async showToast(message: string, color: 'success' | 'warning' | 'danger') {
const toast = await this.toastController.create({
message,
duration: 3000,
color,
position: 'bottom',
buttons: [{ icon: 'close', role: 'cancel' }]
});
await toast.present();
}
Confirmation Alerts
async cancelQuest() {
if (this.isEditing) {
const alert = await this.alertController.create({
header: 'Cancelar Edición',
message: '¿Estás seguro de que deseas cancelar?',
buttons: ['Seguir Editando', 'Cancelar']
});
await alert.present();
}
}
Usage Example
Creating a New Quest
import { NewQuestComponent } from './components/edits/new-quest/new-quest.component';
@Component({
template: `<app-new-quest></app-new-quest>`
})
export class NewQuestPage {}
Editing an Existing Quest
@Component({
template: `
<app-new-quest [questToEdit]="selectedQuest"></app-new-quest>
`
})
export class EditQuestPage {
selectedQuest: Quest = {
id: '123',
title: 'Complete documentation',
category: 'frontend',
difficulty: 2,
xpReward: 500
};
}
Integration with Services
- QuestService: CRUD operations for quests
- QuestSharedService: Triggers refresh in other components
- CategoryService: Loads available categories
constructor(
private questService: QuestService,
private questSharedService: QuestSharedService,
private categoryService: CategoryService
) {}
ngOnInit() {
this.categories = this.categoryService.getAll();
}