From c0f13495ce6af6b053eea228cae3adbd3de42f3d Mon Sep 17 00:00:00 2001 From: igor Date: Mon, 15 Jun 2026 05:05:41 +0200 Subject: [PATCH] implemented step 19 by Gemini - Preview and Export --- public/ajax.php | 8 +++ public/css/wizard.css | 96 ++++++++++++++++++++++++++++++++++ public/index.html | 42 ++++++++++++--- public/js/wizard.js | 57 ++++++++++++++++++++ src/Actions/ProjectActions.php | 51 ++++++++++++++++++ 5 files changed, 246 insertions(+), 8 deletions(-) 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.

-
-

Obsah pre krok 7...

+
+
+

Náhľad vášho webu

+

Pozrite si, ako vyzerá váš nový web. Môžete prepínať medzi zobrazením pre počítač a mobil.

+
+
+ + +
+
+ +
+
-

Hotovo!

-

Váš web je pripravený na stiahnutie.

-
-

Obsah pre krok 8...

+
+
🎉
+

Váš web je pripravený!

+

Gratulujeme! Váš nový web bol úspešne vygenerovaný a je pripravený na stiahnutie.

+ +
+ +
+ +
+
+ +
+

Čo robiť po stiahnutí?

+
    +
  1. Rozbaľte stiahnutý .zip archív vo vašom počítači.
  2. +
  3. Nahrajte všetky súbory a priečinky na váš webhosting cez FTP.
  4. +
  5. Váš web bude okamžite funkčný na vašej doméne.
  6. +
  7. Ak ste zvolili Lokálny mód formulára, správy nájdete na adrese /form-viewer.php.
  8. +
+
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 + ]; + } }