tc-agent / static /js /app.js
togitoon's picture
Initial
bf5f290
/**
* Main Application File
* Orchestrates all modules and manages application state
*/
class TopcoderChallengeApp {
constructor() {
// Initialize modules
this.ui = new UIComponents();
this.userManager = new UserManager(apiClient, this.ui);
this.challengeManager = new ChallengeManager(apiClient, this.ui);
// Current user data
this.currentUserData = null;
// DOM elements
this.configSection = document.getElementById('configSection');
this.resultsSection = document.getElementById('resultsSection');
this.configForm = document.getElementById('configForm');
this.emailInput = document.getElementById('email');
this.preferencesInput = document.getElementById('preferences');
this.displayEmail = document.getElementById('displayEmail');
this.displayPreferences = document.getElementById('displayPreferences');
// Initialize application
this.init();
}
/**
* Initialize the application
*/
init() {
this.setupEventListeners();
this.setupKeyboardShortcuts();
this.setupFormEnhancements();
// Check for prefilled email on page load
this.checkPrefilledEmail();
console.log('Topcoder Challenge Steward Agent - UI Ready!');
}
/**
* Setup event listeners
*/
setupEventListeners() {
// Form submission
this.configForm?.addEventListener('submit', (e) => this.handleFormSubmit(e));
// Email input handlers
this.emailInput?.addEventListener('blur', () => this.handleEmailChange());
this.emailInput?.addEventListener('input', this.ui.debounce(() => this.handleEmailInput(), 1200));
// Button handlers
this.setupButtonHandlers();
}
/**
* Setup button event handlers
*/
setupButtonHandlers() {
const buttons = {
'editPreferences': () => this.editPreferences(),
'registerForNotifications': () => this.registerForNotifications(),
'refreshChallenges': () => this.refreshChallenges(),
'newSearch': () => this.newSearch()
};
Object.entries(buttons).forEach(([id, handler]) => {
const element = document.getElementById(id);
if (element) {
element.addEventListener('click', handler);
}
});
}
/**
* Setup keyboard shortcuts
*/
setupKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// Enter key in textarea to submit form (without shift)
if (e.key === 'Enter' && e.target === this.preferencesInput && !e.shiftKey) {
e.preventDefault();
this.configForm?.dispatchEvent(new Event('submit'));
}
});
}
/**
* Setup form enhancements
*/
setupFormEnhancements() {
// Auto-resize textarea
this.preferencesInput?.addEventListener('input', () => {
this.ui.autoResizeTextarea(this.preferencesInput);
});
}
/**
* Handle form submission
*/
async handleFormSubmit(e) {
e.preventDefault();
const email = this.emailInput?.value.trim();
const preferences = this.preferencesInput?.value.trim();
// Validate form
if (!this.validateForm()) {
return;
}
// Store current user data
this.currentUserData = { email, preferences };
try {
// Fetch and display challenges
await this.challengeManager.fetchAndDisplayChallenges(preferences);
// Check user status for scheduling buttons
await this.checkUserStatus();
// Show results
this.showResults();
} catch (error) {
console.error('Error in form submission:', error);
this.ui.showError(`Failed to fetch challenges: ${error.message}`);
}
}
/**
* Validate form inputs
*/
validateForm() {
const emailValid = this.ui.validateField(this.emailInput, {
required: true,
email: true,
name: 'Email'
});
return emailValid;
}
/**
* Handle email input change (debounced)
*/
async handleEmailInput() {
const email = this.emailInput?.value.trim();
if (email && this.userManager.validateEmail(email)) {
await this.fetchExistingUserPreferencesQuiet(email);
} else {
this.userManager.removeUserStatusDisplay();
}
}
/**
* Handle email blur event
*/
async handleEmailChange() {
const email = this.emailInput?.value.trim();
if (email && this.userManager.validateEmail(email)) {
await this.fetchExistingUserPreferences(email);
}
}
/**
* Fetch existing user preferences
*/
async fetchExistingUserPreferences(email) {
try {
// Show loading indicator for user fetch
this.ui.showLoading('Loading user data...');
const user = await this.userManager.autoFillPreferences(email, this.preferencesInput);
if (user) {
this.showUserStatusInForm(user);
this.ui.showInfoMessage('Found existing account for this email address');
} else {
this.userManager.removeUserStatusDisplay();
}
} catch (error) {
console.log('Could not fetch existing user preferences:', error.message);
this.userManager.removeUserStatusDisplay();
} finally {
// Hide loading indicator
this.ui.hideLoading();
}
}
/**
* Show user status in form area
*/
showUserStatusInForm(user) {
// Remove existing status display
this.userManager.removeUserStatusDisplay();
// Create new status display
const statusContainer = this.userManager.createUserStatusDisplay(
user,
() => this.handleDeactivateFromForm(),
() => this.handleReactivateFromForm()
);
// Insert after the form
this.configForm?.parentNode?.insertBefore(statusContainer, this.configForm.nextSibling);
}
/**
* Handle deactivate from form area
*/
async handleDeactivateFromForm() {
const email = this.emailInput?.value.trim();
if (!email) {
this.ui.showError('Email is required');
return;
}
try {
// Show loading indicator
this.ui.showLoading('Deactivating notifications...');
await this.userManager.deactivateUser(email);
// Refresh the user status display (without additional loading as we already show it)
const user = await this.userManager.autoFillPreferences(email, this.preferencesInput);
if (user) {
this.showUserStatusInForm(user);
} else {
this.userManager.removeUserStatusDisplay();
}
} catch (error) {
// Error already shown by userManager
} finally {
this.ui.hideLoading();
}
}
/**
* Handle reactivate from form area
*/
async handleReactivateFromForm() {
const email = this.emailInput?.value.trim();
if (!email) {
this.ui.showError('Email is required');
return;
}
try {
// Show loading indicator
this.ui.showLoading('Reactivating notifications...');
await this.userManager.reactivateUser(email);
// Refresh the user status display (without additional loading as we already show it)
const user = await this.userManager.autoFillPreferences(email, this.preferencesInput);
if (user) {
this.showUserStatusInForm(user);
} else {
this.userManager.removeUserStatusDisplay();
}
} catch (error) {
// Error already shown by userManager
} finally {
this.ui.hideLoading();
}
}
/**
* Show results section
*/
showResults() {
if (this.currentUserData) {
// Update display information
if (this.displayEmail) {
this.displayEmail.textContent = this.currentUserData.email;
}
if (this.displayPreferences) {
this.displayPreferences.textContent = this.currentUserData.preferences || 'No specific preferences';
}
}
// Show results section and hide config
if (this.configSection) {
this.configSection.style.display = 'none';
}
if (this.resultsSection) {
this.resultsSection.style.display = 'block';
}
}
/**
* Edit preferences (go back to form)
*/
editPreferences() {
if (this.currentUserData) {
// Pre-fill the form with current data
if (this.emailInput) {
this.emailInput.value = this.currentUserData.email;
}
if (this.preferencesInput) {
this.preferencesInput.value = this.currentUserData.preferences;
this.ui.autoResizeTextarea(this.preferencesInput);
}
}
// Show config section and hide results
if (this.configSection) {
this.configSection.style.display = 'block';
}
if (this.resultsSection) {
this.resultsSection.style.display = 'none';
}
// Focus on preferences field
this.preferencesInput?.focus();
}
/**
* Refresh challenges
*/
async refreshChallenges() {
if (!this.currentUserData) {
this.ui.showError('No current search to refresh');
return;
}
try {
await this.challengeManager.fetchAndDisplayChallenges(this.currentUserData.preferences);
this.ui.showSuccessMessage('Challenges refreshed successfully!');
} catch (error) {
console.error('Error refreshing challenges:', error);
this.ui.showError(`Failed to refresh challenges: ${error.message}`);
}
}
/**
* Start new search
*/
newSearch() {
// Reset form and data
if (this.emailInput) {
this.emailInput.value = '';
}
if (this.preferencesInput) {
this.preferencesInput.value = '';
}
this.currentUserData = null;
// Clear challenges
this.challengeManager.clearChallenges();
// Hide user status display
this.userManager.removeUserStatusDisplay();
// Show config section and hide results
if (this.configSection) {
this.configSection.style.display = 'block';
}
if (this.resultsSection) {
this.resultsSection.style.display = 'none';
}
// Focus on email field
this.emailInput?.focus();
}
/**
* Register for daily notifications
*/
async registerForNotifications() {
if (!this.currentUserData) {
this.ui.showError('No user data available');
return;
}
try {
// Show loading indicator
this.ui.showLoading('Registering for daily notifications...');
await this.userManager.registerOrUpdateUser(
this.currentUserData.email,
this.currentUserData.preferences
);
// Update schedule button state
this.updateScheduleButtonState(true);
} catch (error) {
console.error('Error registering for notifications:', error);
this.ui.showError(`Failed to register for notifications: ${error.message}`);
} finally {
this.ui.hideLoading();
}
}
/**
* Check user status for scheduling
*/
async checkUserStatus() {
if (!this.currentUserData || !this.currentUserData.email) {
return;
}
try {
const user = await this.userManager.loadUserByEmail(this.currentUserData.email);
if (user && user.active) {
this.updateScheduleButtonState(true);
} else {
this.updateScheduleButtonState(false);
}
} catch (error) {
console.log('Could not check user status:', error.message);
}
}
/**
* Update registration button state
*/
updateScheduleButtonState(isActive) {
const registerBtn = document.getElementById('registerForNotifications');
const unregisterBtn = document.getElementById('unregisterBtn');
if (registerBtn) {
if (isActive) {
registerBtn.innerHTML = '<i class="fas fa-check"></i> Registered for Daily Notifications';
registerBtn.classList.add('btn-success');
registerBtn.classList.remove('btn-primary');
registerBtn.disabled = true;
// Add unregister button if it doesn't exist
if (!unregisterBtn) {
this.addUnregisterButton();
}
} else {
registerBtn.innerHTML = '<i class="fas fa-bell"></i> Register for Daily Notifications';
registerBtn.classList.remove('btn-success');
registerBtn.classList.add('btn-primary');
registerBtn.disabled = false;
// Remove unregister button
if (unregisterBtn) {
unregisterBtn.remove();
}
}
}
}
/**
* Add unregister button
*/
addUnregisterButton() {
const existingBtn = document.getElementById('unregisterBtn');
if (existingBtn) {
return;
}
const unregisterBtn = document.createElement('button');
unregisterBtn.id = 'unregisterBtn';
unregisterBtn.className = 'btn btn-danger';
unregisterBtn.innerHTML = '<i class="fas fa-times"></i> Deactivate';
unregisterBtn.style.marginLeft = '0.5rem';
// Add click handler
unregisterBtn.addEventListener('click', () => this.handleUnregister());
// Add to results actions
const resultsActions = document.querySelector('.results-actions');
if (resultsActions) {
resultsActions.appendChild(unregisterBtn);
}
}
/**
* Handle unregister
*/
async handleUnregister() {
if (!this.currentUserData || !this.currentUserData.email) {
this.ui.showError('No user data available');
return;
}
try {
await this.userManager.deactivateUser(this.currentUserData.email);
this.updateScheduleButtonState(false);
this.userManager.removeUserStatusDisplay();
} catch (error) {
// Error already shown by userManager
}
}
/**
* Check for prefilled email on page load
*/
async checkPrefilledEmail() {
// Small delay to ensure DOM is fully loaded
setTimeout(async () => {
const email = this.emailInput?.value.trim();
if (email && this.userManager.validateEmail(email)) {
console.log('Found prefilled email on page load:', email);
await this.fetchExistingUserPreferencesWithLoading(email);
}
}, 100);
}
/**
* Fetch existing user preferences with loading indicator
*/
async fetchExistingUserPreferencesWithLoading(email) {
try {
// Show loading indicator
this.ui.showLoading('Loading user data...');
const user = await this.userManager.autoFillPreferences(email, this.preferencesInput);
if (user) {
this.showUserStatusInForm(user);
this.ui.showInfoMessage('Found existing account for this email address');
} else {
this.userManager.removeUserStatusDisplay();
}
} catch (error) {
console.log('Could not fetch existing user preferences:', error.message);
this.userManager.removeUserStatusDisplay();
} finally {
// Hide loading indicator
this.ui.hideLoading();
}
}
/**
* Fetch existing user preferences without loading indicator (for typing)
*/
async fetchExistingUserPreferencesQuiet(email) {
try {
const user = await this.userManager.autoFillPreferences(email, this.preferencesInput);
if (user) {
this.showUserStatusInForm(user);
// Don't show info message for quiet fetch to avoid spam while typing
} else {
this.userManager.removeUserStatusDisplay();
}
} catch (error) {
console.log('Could not fetch existing user preferences:', error.message);
this.userManager.removeUserStatusDisplay();
}
}
}
// Initialize the application when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
new TopcoderChallengeApp();
});