/**
* WebWizard Frontend Logic
*/
const App = {
state: {
userId: localStorage.getItem('ww_user_id'),
currentStep: 1,
totalSteps: 8,
project: null,
categories: [],
selection: {
category: null,
subcategory: null,
customDescription: '',
style: null,
palette: null,
logo: null,
gallery: []
}
},
async init() {
console.log('Initializing WebWizard...');
if (!this.state.userId) {
await this.initSession();
}
await this.loadCategories();
this.bindEvents();
// If we don't have a project, try to load existing or create one
if (!this.state.project) {
await this.loadLastProject();
}
this.showStep(this.state.currentStep);
},
async loadLastProject() {
try {
const response = await this.apiCall('listProjects');
if (response.success && response.data.length > 0) {
// Load the most recent project
const lastProject = response.data[0];
const statusResponse = await this.apiCall('getProjectStatus', {}, lastProject.project_id);
if (statusResponse.success) {
this.state.project = statusResponse.data;
this.state.currentStep = this.state.project.current_step || 1;
console.log('Loaded existing project:', this.state.project.project_id);
this.syncSelectionWithProject();
// Resume polling if generating
const pollingStatuses = ['queued', 'generating', 'rendering'];
if (pollingStatuses.includes(this.state.project.status)) {
this.state.currentStep = 6;
this.startPolling();
}
}
} else {
await this.createProject();
}
} catch (error) {
console.error('Failed to load projects:', error);
await this.createProject();
}
},
syncSelectionWithProject() {
if (this.state.project && this.state.project.wizard_data) {
const wd = this.state.project.wizard_data;
// Step 1
if (wd.business_category) {
const bc = wd.business_category;
this.state.selection.category = bc.group;
this.state.selection.subcategory = bc.subcategory;
this.state.selection.customDescription = bc.custom_description || '';
if (this.state.selection.category) {
this.selectCategory(this.state.selection.category);
this.state.selection.subcategory = bc.subcategory;
this.renderSubcategories(this.state.selection.category);
}
}
// Step 2
if (wd.identity) {
document.getElementById('business-name').value = wd.identity.business_name || '';
document.getElementById('business-tagline').value = wd.identity.tagline || '';
document.getElementById('business-description').value = wd.identity.description || '';
}
if (wd.contact) {
document.getElementById('contact-email').value = wd.contact.email || '';
document.getElementById('contact-phone').value = wd.contact.phone || '';
document.getElementById('contact-address').value = wd.contact.address || '';
document.getElementById('contact-city').value = wd.contact.city || '';
document.getElementById('contact-facebook').value = wd.contact.socials?.facebook || '';
document.getElementById('contact-instagram').value = wd.contact.socials?.instagram || '';
}
// Step 3
if (wd.services && wd.services.items) {
const list = document.getElementById('services-list');
list.innerHTML = '';
wd.services.items.forEach(item => this.addServiceItem(item));
document.getElementById('pricing-note').value = wd.services.pricing_note || '';
} else {
this.addServiceItem();
}
// Step 4
if (wd.visuals) {
this.state.selection.style = wd.visuals.style;
this.state.selection.palette = wd.visuals.palette;
this.updateStyleSelection();
this.updatePaletteSelection();
}
if (wd.assets) {
this.state.selection.logo = wd.assets.logo;
this.state.selection.gallery = wd.assets.gallery || [];
if (this.state.selection.logo) this.renderLogoPreview();
this.renderGalleryPreviews();
}
// Step 5
if (wd.modules) {
const m = wd.modules;
if (m.sections) {
m.sections.forEach(sec => {
const el = document.querySelector(`input[data-module="${sec}"]`);
if (el) el.checked = true;
});
}
if (m.contact_form) {
document.getElementById('form-enabled').checked = m.contact_form.enabled;
document.getElementById('form-config-container').classList.toggle('hidden', !m.contact_form.enabled);
if (m.contact_form.mode) {
document.querySelector(`input[name="form-mode"][value="${m.contact_form.mode}"]`).checked = true;
document.getElementById('config-local').classList.toggle('hidden', m.contact_form.mode !== 'local');
document.getElementById('config-smtp').classList.toggle('hidden', m.contact_form.mode !== 'smtp');
}
if (m.contact_form.smtp) {
document.getElementById('smtp-host').value = m.contact_form.smtp.host || '';
document.getElementById('smtp-port').value = m.contact_form.smtp.port || '';
document.getElementById('smtp-user').value = m.contact_form.smtp.user || '';
document.getElementById('smtp-recipient').value = m.contact_form.smtp.recipient || '';
}
}
}
}
},
async initSession() {
try {
const response = await this.apiCall('initSession');
if (response.success) {
this.state.userId = response.data.user_id;
localStorage.setItem('ww_user_id', this.state.userId);
console.log('Session initialized:', this.state.userId);
}
} catch (error) {
console.error('Failed to initialize session:', error);
alert('Chyba pri inicializácii session.');
}
},
async createProject() {
try {
const response = await this.apiCall('createProject');
if (response.success) {
this.state.project = response.data;
console.log('Project created:', this.state.project.project_id);
}
} catch (error) {
console.error('Failed to create project:', error);
}
},
async loadCategories() {
try {
const response = await this.apiCall('getCategories');
if (response.success) {
this.state.categories = response.data.categories;
this.renderCategories();
}
} catch (error) {
console.error('Failed to load categories:', error);
}
},
async apiCall(action, payload = {}, projectId = null) {
const body = {
action,
project_id: projectId || (this.state.project ? this.state.project.project_id : null),
payload
};
const headers = {
'Content-Type': 'application/json'
};
if (this.state.userId) {
headers['X-User-ID'] = this.state.userId;
}
const response = await fetch('/ajax.php', {
method: 'POST',
headers,
body: JSON.stringify(body)
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error.message || 'API Error');
}
return result;
},
bindEvents() {
document.getElementById('btn-next').addEventListener('click', () => this.nextStep());
document.getElementById('btn-prev').addEventListener('click', () => this.prevStep());
document.getElementById('custom-description').addEventListener('input', (e) => {
this.state.selection.customDescription = e.target.value;
});
// Step 2 validation listeners
['business-name', 'gdpr-consent', 'contact-email', 'contact-phone'].forEach(id => {
const el = document.getElementById(id);
if (el) {
el.addEventListener('input', () => this.updateUI());
el.addEventListener('change', () => this.updateUI());
}
});
// Step 3 events
document.getElementById('btn-add-service').addEventListener('click', () => this.addServiceItem());
// Step 4 events
document.querySelectorAll('#style-grid .category-card').forEach(card => {
card.addEventListener('click', () => {
this.state.selection.style = card.getAttribute('data-style');
this.updateStyleSelection();
});
});
document.querySelectorAll('#palette-list .palette-card').forEach(card => {
card.addEventListener('click', () => {
this.state.selection.palette = card.getAttribute('data-palette');
this.updatePaletteSelection();
});
});
document.getElementById('logo-upload-box').addEventListener('click', () => document.getElementById('logo-input').click());
document.getElementById('logo-input').addEventListener('change', (e) => this.handleFileUpload(e, 'logo'));
document.getElementById('gallery-add-box').addEventListener('click', () => document.getElementById('gallery-input').click());
document.getElementById('gallery-input').addEventListener('change', (e) => this.handleFileUpload(e, 'gallery'));
// Step 5 events
document.getElementById('form-enabled').addEventListener('change', (e) => {
document.getElementById('form-config-container').classList.toggle('hidden', !e.target.checked);
});
document.querySelectorAll('input[name="form-mode"]').forEach(radio => {
radio.addEventListener('change', (e) => {
document.getElementById('config-local').classList.toggle('hidden', e.target.value !== 'local');
document.getElementById('config-smtp').classList.toggle('hidden', e.target.value !== 'smtp');
});
});
},
renderCategories() {
const container = document.getElementById('category-list');
container.innerHTML = '';
const icons = {
gastro: '🍕',
beauty: '✨',
crafts: '🛠️',
professional: '💼',
other: '❓'
};
this.state.categories.forEach(cat => {
const card = document.createElement('div');
card.className = 'category-card';
if (this.state.selection.category === cat.id) card.classList.add('selected');
card.innerHTML = `
${icons[cat.id] || '📁'}
${cat.name}
`;
card.addEventListener('click', () => this.selectCategory(cat.id));
container.appendChild(card);
});
},
selectCategory(categoryId) {
this.state.selection.category = categoryId;
this.state.selection.subcategory = null;
this.renderCategories();
this.renderSubcategories(categoryId);
this.renderSmartQuestions(categoryId);
document.getElementById('subcategory-container').classList.remove('hidden');
if (categoryId === 'other') {
document.getElementById('custom-category-group').classList.remove('hidden');
} else {
document.getElementById('custom-category-group').classList.add('hidden');
}
},
renderSubcategories(categoryId) {
const container = document.getElementById('subcategory-list');
container.innerHTML = '';
const category = this.state.categories.find(c => c.id === categoryId);
if (!category) return;
category.subcategories.forEach(sub => {
const chip = document.createElement('div');
chip.className = 'chip';
if (this.state.selection.subcategory === sub.id) chip.classList.add('selected');
chip.textContent = sub.name;
chip.addEventListener('click', () => {
this.state.selection.subcategory = sub.id;
this.renderSubcategories(categoryId);
});
container.appendChild(chip);
});
},
addServiceItem(data = {}) {
const container = document.getElementById('services-list');
const itemDiv = document.createElement('div');
itemDiv.className = 'service-item';
itemDiv.innerHTML = `
`;
itemDiv.querySelector('.btn-remove-service').addEventListener('click', () => {
if (container.querySelectorAll('.service-item').length > 1) {
itemDiv.remove();
} else {
alert('Zadajte aspoň jednu službu.');
}
});
container.appendChild(itemDiv);
},
renderSmartQuestions(categoryId) {
const container = document.getElementById('smart-questions-list');
container.innerHTML = '';
const category = this.state.categories.find(c => c.id === categoryId);
if (!category || !category.smart_questions) {
document.getElementById('smart-questions-container').classList.add('hidden');
return;
}
document.getElementById('smart-questions-container').classList.remove('hidden');
category.smart_questions.forEach(q => {
const qDiv = document.createElement('div');
qDiv.className = 'smart-question-item';
if (q.type === 'boolean') {
qDiv.innerHTML = `
`;
} else {
qDiv.innerHTML = `
`;
}
container.appendChild(qDiv);
// Apply existing answer if available
if (this.state.project && this.state.project.wizard_data.smart_answers) {
const answer = this.state.project.wizard_data.smart_answers[q.id];
if (answer !== undefined) {
const input = qDiv.querySelector(`#sq-${q.id}`);
if (q.type === 'boolean') {
input.checked = !!answer;
} else {
input.value = answer;
}
}
}
});
},
updateStyleSelection() {
document.querySelectorAll('#style-grid .category-card').forEach(card => {
card.classList.toggle('selected', card.getAttribute('data-style') === this.state.selection.style);
});
this.updateUI();
},
updatePaletteSelection() {
document.querySelectorAll('#palette-list .palette-card').forEach(card => {
card.classList.toggle('selected', card.getAttribute('data-palette') === this.state.selection.palette);
});
this.updateUI();
},
async handleFileUpload(event, target) {
const files = event.target.files;
if (!files.length) return;
for (const file of files) {
const formData = new FormData();
formData.append('action', 'uploadAsset');
formData.append('project_id', this.state.project.project_id);
formData.append('file', file);
formData.append('X-User-ID', this.state.userId);
try {
const response = await fetch('/ajax.php', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
if (target === 'logo') {
this.state.selection.logo = result.data.path;
this.renderLogoPreview();
} else {
if (this.state.selection.gallery.length < 5) {
this.state.selection.gallery.push(result.data.path);
this.renderGalleryPreviews();
} else {
alert('Maximálne 5 fotografií.');
}
}
} else {
alert('Upload zlyhal: ' + result.error.message);
}
} catch (error) {
console.error('Upload error:', error);
alert('Chyba pri nahrávaní.');
}
}
this.updateUI();
},
renderLogoPreview() {
const preview = document.getElementById('logo-preview');
preview.innerHTML = `
`;
preview.classList.remove('hidden');
document.querySelector('#logo-upload-box .upload-placeholder').classList.add('hidden');
document.getElementById('btn-remove-logo').addEventListener('click', (e) => {
e.stopPropagation();
this.removeAsset('logo');
});
},
renderGalleryPreviews() {
const container = document.getElementById('gallery-previews');
container.innerHTML = '';
this.state.selection.gallery.forEach((path, index) => {
const item = document.createElement('div');
item.className = 'gallery-item';
item.innerHTML = `
`;
item.querySelector('.btn-remove-asset').addEventListener('click', (e) => {
e.stopPropagation();
this.removeAsset('gallery', index);
});
container.appendChild(item);
});
},
removeAsset(target, index = null) {
if (target === 'logo') {
this.state.selection.logo = null;
document.getElementById('logo-preview').classList.add('hidden');
document.querySelector('#logo-upload-box .upload-placeholder').classList.remove('hidden');
} else {
this.state.selection.gallery.splice(index, 1);
this.renderGalleryPreviews();
}
this.updateUI();
},
showStep(n) {
const steps = document.querySelectorAll('.step');
steps.forEach(step => step.classList.remove('active'));
const activeStep = document.querySelector(`.step[data-step="${n}"]`);
if (activeStep) {
activeStep.classList.add('active');
}
this.state.currentStep = n;
this.updateUI();
},
async nextStep() {
if (this.state.currentStep === 1) {
if (!this.state.selection.category || !this.state.selection.subcategory) {
alert('Prosím, vyberte kategóriu aj podkategóriu.');
return;
}
try {
await this.apiCall('saveStep', {
step: 1,
data: {
business_category: {
group: this.state.selection.category,
subcategory: this.state.selection.subcategory,
custom_description: this.state.selection.category === 'other' ? this.state.selection.customDescription : null
}
}
});
} catch (error) {
console.error('Save step failed:', error);
alert('Nepodarilo sa uložiť dáta.');
return;
}
} else if (this.state.currentStep === 2) {
const businessName = document.getElementById('business-name').value;
const tagline = document.getElementById('business-tagline').value;
const description = document.getElementById('business-description').value;
const email = document.getElementById('contact-email').value;
const phone = document.getElementById('contact-phone').value;
const address = document.getElementById('contact-address').value;
const city = document.getElementById('contact-city').value;
const gdpr = document.getElementById('gdpr-consent').checked;
if (!businessName || !gdpr || (!email && !phone)) {
alert('Prosím, vyplňte povinné údaje a zaškrtnite GDPR súhlas.');
return;
}
try {
// 1. Save Consent
await this.apiCall('saveConsent', {
consent_text: document.querySelector('label[for="gdpr-consent"]').textContent
});
// 2. Save Step 2
await this.apiCall('saveStep', {
step: 2,
data: {
identity: {
business_name: businessName,
tagline: tagline,
description: description
},
contact: {
email: email,
phone: phone,
address: address,
city: city,
socials: {
facebook: document.getElementById('contact-facebook').value,
instagram: document.getElementById('contact-instagram').value
}
}
}
});
} catch (error) {
console.error('Save step 2 failed:', error);
alert('Nepodarilo sa uložiť dáta: ' + error.message);
return;
}
} else if (this.state.currentStep === 3) {
const serviceItems = [];
document.querySelectorAll('.service-item').forEach(item => {
const name = item.querySelector('.service-name').value;
if (name) {
serviceItems.push({
name: name,
price_from: item.querySelector('.service-price').value,
description: item.querySelector('.service-desc').value
});
}
});
const smartAnswers = {};
document.querySelectorAll('.smart-question-item input').forEach(input => {
const id = input.getAttribute('data-id');
if (input.type === 'checkbox') {
smartAnswers[id] = input.checked;
} else {
smartAnswers[id] = input.value;
}
});
try {
await this.apiCall('saveStep', {
step: 3,
data: {
services: {
items: serviceItems,
pricing_note: document.getElementById('pricing-note').value
},
smart_answers: smartAnswers
}
});
} catch (error) {
console.error('Save step 3 failed:', error);
alert('Nepodarilo sa uložiť dáta: ' + error.message);
return;
}
} else if (this.state.currentStep === 4) {
if (!this.state.selection.style || !this.state.selection.palette) {
alert('Prosím, vyberte vizuálny štýl a farebnú paletu.');
return;
}
try {
await this.apiCall('saveStep', {
step: 4,
data: {
visuals: {
style: this.state.selection.style,
palette: this.state.selection.palette
},
assets: {
logo: this.state.selection.logo,
gallery: this.state.selection.gallery
}
}
});
} catch (error) {
console.error('Save step 4 failed:', error);
alert('Nepodarilo sa uložiť dáta: ' + error.message);
return;
}
} else if (this.state.currentStep === 5) {
const sections = [];
document.querySelectorAll('input[data-module]:checked').forEach(el => {
sections.push(el.getAttribute('data-module'));
});
const formEnabled = document.getElementById('form-enabled').checked;
const formMode = document.querySelector('input[name="form-mode"]:checked').value;
const modulesData = {
pages: ['home'],
sections: sections,
contact_form: {
enabled: formEnabled,
mode: formMode,
smtp: formMode === 'smtp' ? {
host: document.getElementById('smtp-host').value,
port: document.getElementById('smtp-port').value,
user: document.getElementById('smtp-user').value,
pass: document.getElementById('smtp-pass').value,
recipient: document.getElementById('smtp-recipient').value
} : null,
local_viewer: formMode === 'local' ? {
password: document.getElementById('local-password').value
} : null
}
};
try {
await this.apiCall('saveStep', {
step: 5,
data: {
modules: modulesData
}
});
} catch (error) {
console.error('Save step 5 failed:', error);
alert('Nepodarilo sa uložiť dáta: ' + error.message);
return;
}
}
if (this.state.currentStep < this.state.totalSteps) {
this.showStep(this.state.currentStep + 1);
// Trigger generation if entering step 6
if (this.state.currentStep === 6) {
this.startWebsiteGeneration();
}
}
},
async startWebsiteGeneration() {
try {
const result = await this.apiCall('generateWebsite');
if (result.success) {
console.log('Generation started:', result.data.task_id);
this.startPolling();
}
} catch (error) {
console.error('Failed to start generation:', error);
document.getElementById('generation-status').textContent = 'Chyba: ' + error.message;
}
},
startPolling() {
if (this.pollingInterval) clearInterval(this.pollingInterval);
this.pollingInterval = setInterval(async () => {
try {
const response = await this.apiCall('getProjectStatus');
if (response.success) {
const project = response.data;
this.state.project = project;
this.updateGenerationStatus(project.status);
if (project.status === 'ready' || project.status === 'failed') {
clearInterval(this.pollingInterval);
if (project.status === 'ready') {
// Move to preview step
this.showStep(7);
} else {
document.getElementById('generation-status').textContent = 'Generovanie zlyhalo. Skúste to neskôr.';
}
}
}
} catch (error) {
console.error('Polling error:', error);
}
}, 10000);
},
updateGenerationStatus(status) {
const statusEl = document.getElementById('generation-status');
const messages = {
'queued': 'Zaradené do fronty, čakám na AI...',
'generating': 'AI práve píše texty pre váš web...',
'rendering': 'Skladáme výslednú stránku...',
'ready': 'Hotovo! Pripravujem náhľad...',
'failed': 'Nastal problém pri generovaní.'
};
statusEl.textContent = messages[status] || 'Spracovávam...';
},
prevStep() {
if (this.state.currentStep > 1) {
this.showStep(this.state.currentStep - 1);
}
},
updateUI() {
const btnPrev = document.getElementById('btn-prev');
const btnNext = document.getElementById('btn-next');
btnPrev.disabled = this.state.currentStep === 1;
// Validation for Next button
let nextDisabled = false;
if (this.state.currentStep === 1) {
nextDisabled = !this.state.selection.category || !this.state.selection.subcategory;
} else if (this.state.currentStep === 2) {
const name = document.getElementById('business-name').value;
const email = document.getElementById('contact-email').value;
const phone = document.getElementById('contact-phone').value;
const gdpr = document.getElementById('gdpr-consent').checked;
nextDisabled = !name || !gdpr || (!email && !phone);
} else if (this.state.currentStep === 4) {
nextDisabled = !this.state.selection.style || !this.state.selection.palette;
} else if (this.state.currentStep === 5) {
const formEnabled = document.getElementById('form-enabled').checked;
if (formEnabled) {
const mode = document.querySelector('input[name="form-mode"]:checked').value;
if (mode === 'local') {
nextDisabled = !document.getElementById('local-password').value;
} else {
nextDisabled = !document.getElementById('smtp-host').value || !document.getElementById('smtp-recipient').value;
}
}
}
btnNext.disabled = nextDisabled;
// Hide footer in generating step
document.querySelector('.wizard-footer').classList.toggle('hidden', this.state.currentStep === 6);
if (this.state.currentStep === this.state.totalSteps) {
btnNext.textContent = 'Dokončiť';
} else {
btnNext.textContent = 'Pokračovať';
}
const progressFill = document.querySelector('.progress-fill');
const percent = ((this.state.currentStep - 1) / (this.state.totalSteps - 1)) * 100;
progressFill.style.width = `${percent}%`;
}
};
document.addEventListener('DOMContentLoaded', () => App.init());