diff --git a/public/ajax.php b/public/ajax.php
index 5ff48bd..03dc516 100644
--- a/public/ajax.php
+++ b/public/ajax.php
@@ -143,6 +143,14 @@ try {
sendResponse(true, $result);
break;
+ case 'exportWebsite':
+ $projectId = $data['project_id'] ?? null;
+ if (!$projectId) {
+ sendResponse(false, ['code' => 'MISSING_PROJECT_ID', 'message' => 'Project ID is required.'], 400);
+ }
+ sendResponse(true, $projectActions->exportWebsite($userId, $projectId));
+ break;
+
default:
sendResponse(false, ['code' => 'UNKNOWN_ACTION', 'message' => "Action '$action' is not defined."], 404);
break;
diff --git a/public/css/wizard.css b/public/css/wizard.css
index b5b1276..d88ed23 100644
--- a/public/css/wizard.css
+++ b/public/css/wizard.css
@@ -509,6 +509,102 @@ textarea:focus, input[type="text"]:focus, input[type="email"]:focus, input[type=
color: var(--primary-color);
}
+/* Preview Step */
+.step-header-with-actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.device-toggle {
+ display: flex;
+ background-color: #f1f5f9;
+ padding: 0.25rem;
+ border-radius: 0.5rem;
+ white-space: nowrap;
+}
+
+.btn-toggle {
+ padding: 0.4rem 0.8rem;
+ border-radius: 0.375rem;
+ font-size: 0.75rem;
+ border: none;
+ background: transparent;
+ color: var(--text-muted);
+}
+
+.btn-toggle.active {
+ background-color: white;
+ color: var(--primary-color);
+ box-shadow: 0 1px 2px rgba(0,0,0,0.05);
+ font-weight: 600;
+}
+
+.preview-window {
+ border: 1px solid var(--border-color);
+ border-radius: 0.5rem;
+ background-color: #e2e8f0;
+ overflow: hidden;
+ transition: all 0.3s ease;
+ margin: 0 auto;
+}
+
+.preview-window iframe {
+ width: 100%;
+ height: 600px;
+ border: none;
+ background-color: white;
+ display: block;
+}
+
+.preview-window.mobile {
+ max-width: 375px;
+}
+
+/* Success / Export Screen */
+.success-screen {
+ text-align: center;
+ padding: 1rem 0;
+}
+
+.success-icon {
+ font-size: 4rem;
+ margin-bottom: 1rem;
+}
+
+.export-actions {
+ margin: 2.5rem 0;
+}
+
+.btn-large {
+ padding: 1rem 2rem;
+ font-size: 1.125rem;
+}
+
+.instructions-box {
+ background-color: #f8fafc;
+ border: 1px solid var(--border-color);
+ border-radius: 0.75rem;
+ padding: 2rem;
+ text-align: left;
+}
+
+.instructions-box h3 {
+ margin-bottom: 1rem;
+ font-size: 1rem;
+}
+
+.instructions-box ol {
+ padding-left: 1.25rem;
+ color: #475569;
+}
+
+.instructions-box li {
+ margin-bottom: 0.75rem;
+}
+
button:disabled {
opacity: 0.5;
cursor: not-allowed;
diff --git a/public/index.html b/public/index.html
index 1601de3..c911739 100644
--- a/public/index.html
+++ b/public/index.html
@@ -317,19 +317,45 @@
-
Náhľad webu
-
Pozrite si, ako vyzerá váš nový web.
-
-
Hotovo!
-
Váš web je pripravený na stiahnutie.
-
diff --git a/public/js/wizard.js b/public/js/wizard.js
index 789358a..810df5d 100644
--- a/public/js/wizard.js
+++ b/public/js/wizard.js
@@ -273,6 +273,24 @@ const App = {
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() {
@@ -534,9 +552,48 @@ const App = {
}
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) {
diff --git a/src/Actions/ProjectActions.php b/src/Actions/ProjectActions.php
index 8fc7e0d..bc222f0 100644
--- a/src/Actions/ProjectActions.php
+++ b/src/Actions/ProjectActions.php
@@ -257,4 +257,55 @@ class ProjectActions
'mime_type' => $mimeType
];
}
+
+ /**
+ * Creates a ZIP archive of the generated website.
+ */
+ public function exportWebsite(string $userId, string $projectId): array
+ {
+ // 1. Verify ownership
+ $this->getProjectStatus($userId, $projectId);
+
+ $projectExportDir = __DIR__ . "/../../exports/{$projectId}";
+ if (!is_dir($projectExportDir)) {
+ throw new Exception("Generated website not found. Please generate it first.", 404);
+ }
+
+ $zipFileName = "web-" . $projectId . ".zip";
+ $zipPath = $projectExportDir . "/" . $zipFileName;
+
+ $zip = new \ZipArchive();
+ if ($zip->open($zipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
+ throw new Exception("Could not create ZIP archive.", 500);
+ }
+
+ // Recursively add files
+ $files = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($projectExportDir, \RecursiveDirectoryIterator::SKIP_DOTS),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ );
+
+ foreach ($files as $name => $file) {
+ // Skip the ZIP file itself and temporary wizard files if any
+ if (!$file->isDir()) {
+ $filePath = $file->getRealPath();
+ $relativePath = substr($filePath, strlen($projectExportDir) + 1);
+
+ // Don't include the zip we are currently creating
+ if ($relativePath === $zipFileName) continue;
+
+ // Don't include .htaccess if it's in the root (optional, but data/.htaccess should stay)
+ // Actually, we want to include all site files.
+
+ $zip->addFile($filePath, $relativePath);
+ }
+ }
+
+ $zip->close();
+
+ return [
+ 'download_url' => "exports/{$projectId}/" . $zipFileName,
+ 'filename' => $zipFileName
+ ];
+ }
}