Files
WebWizard/public/js/wizard.js

892 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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');
});
});
// Step 7 events
document.querySelectorAll('.btn-toggle').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.btn-toggle').forEach(b => b.classList.remove('active'));
e.target.classList.add('active');
const view = e.target.getAttribute('data-view');
document.getElementById('preview-container').className = 'preview-window ' + view;
});
});
// Step 8 events
document.getElementById('btn-download-zip').addEventListener('click', () => this.downloadWebsite());
document.getElementById('btn-view-live').addEventListener('click', () => {
if (this.state.project) {
window.open(`/exports/${this.state.project.project_id}/index.html`, '_blank');
}
});
},
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 = `
<div class="category-icon">${icons[cat.id] || '📁'}</div>
<div class="category-name">${cat.name}</div>
`;
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');
}
this.updateUI();
},
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);
this.updateUI();
});
container.appendChild(chip);
});
},
addServiceItem(data = {}) {
const container = document.getElementById('services-list');
const itemDiv = document.createElement('div');
itemDiv.className = 'service-item';
itemDiv.innerHTML = `
<div class="service-item-header">
<span class="service-number">Služba</span>
<button class="btn-remove-service" title="Odstrániť">×</button>
</div>
<div class="form-group">
<label>Názov služby *</label>
<input type="text" class="service-name" placeholder="Napr. Klasická masáž" value="${data.name || ''}" required>
</div>
<div class="form-grid">
<div class="form-group">
<label>Cena od (€)</label>
<input type="text" class="service-price" placeholder="25" value="${data.price_from || ''}">
</div>
<div class="form-group">
<label>Stručný popis</label>
<input type="text" class="service-desc" placeholder="Trvanie 45 minút..." value="${data.description || ''}">
</div>
</div>
`;
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 = `
<div class="checkbox-group">
<input type="checkbox" id="sq-${q.id}" data-id="${q.id}">
<label for="sq-${q.id}">${q.name}</label>
</div>
`;
} else {
qDiv.innerHTML = `
<div class="form-group">
<label for="sq-${q.id}">${q.name}</label>
<input type="text" id="sq-${q.id}" data-id="${q.id}" placeholder="Vaša odpoveď...">
</div>
`;
}
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 = `<img src="/${this.state.selection.logo}" alt="Logo">
<button class="btn-remove-asset" id="btn-remove-logo">×</button>`;
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 = `<img src="/${path}" alt="Gallery ${index+1}">
<button class="btn-remove-asset" data-index="${index}">×</button>`;
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;
// Specific step logic
if (n === 7) {
this.loadPreview();
}
this.updateUI();
},
loadPreview() {
const iframe = document.getElementById('preview-iframe');
const timestamp = new Date().getTime();
iframe.src = `/exports/${this.state.project.project_id}/index.html?t=${timestamp}`;
},
async downloadWebsite() {
const btn = document.getElementById('btn-download-zip');
const originalText = btn.textContent;
btn.disabled = true;
btn.textContent = 'Pripravujem archív...';
try {
const result = await this.apiCall('exportWebsite');
if (result.success) {
// Trigger download
const link = document.createElement('a');
link.href = '/' + result.data.download_url;
link.download = result.data.filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
} catch (error) {
console.error('Export failed:', error);
alert('Chyba pri generovaní ZIP archívu.');
} finally {
btn.disabled = false;
btn.textContent = originalText;
}
},
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) {
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);
}
}, 5000);
},
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());