implemented step 19 by Gemini

- Preview and Export
This commit is contained in:
2026-06-15 05:05:41 +02:00
parent 4135b621c4
commit c0f13495ce
5 changed files with 246 additions and 8 deletions

View File

@ -143,6 +143,14 @@ try {
sendResponse(true, $result); sendResponse(true, $result);
break; 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: default:
sendResponse(false, ['code' => 'UNKNOWN_ACTION', 'message' => "Action '$action' is not defined."], 404); sendResponse(false, ['code' => 'UNKNOWN_ACTION', 'message' => "Action '$action' is not defined."], 404);
break; break;

View File

@ -509,6 +509,102 @@ textarea:focus, input[type="text"]:focus, input[type="email"]:focus, input[type=
color: var(--primary-color); 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 { button:disabled {
opacity: 0.5; opacity: 0.5;
cursor: not-allowed; cursor: not-allowed;

View File

@ -317,19 +317,45 @@
<!-- Step 7: Preview --> <!-- Step 7: Preview -->
<div class="step" data-step="7"> <div class="step" data-step="7">
<h2>Náhľad webu</h2> <div class="step-header-with-actions">
<p class="step-description">Pozrite si, ako vyzerá váš nový web.</p> <div>
<div class="form-placeholder"> <h2>Náhľad vášho webu</h2>
<p>Obsah pre krok 7...</p> <p class="step-description">Pozrite si, ako vyzerá váš nový web. Môžete prepínať medzi zobrazením pre počítač a mobil.</p>
</div>
<div class="device-toggle">
<button class="btn-toggle active" data-view="desktop">💻 PC</button>
<button class="btn-toggle" data-view="mobile">📱 Mobil</button>
</div>
</div>
<div id="preview-container" class="preview-window">
<iframe id="preview-iframe" src="about:blank"></iframe>
</div> </div>
</div> </div>
<!-- Step 8: Export --> <!-- Step 8: Export -->
<div class="step" data-step="8"> <div class="step" data-step="8">
<h2>Hotovo!</h2> <div class="success-screen">
<p class="step-description">Váš web je pripravený na stiahnutie.</p> <div class="success-icon">🎉</div>
<div class="form-placeholder"> <h2>Váš web je pripravený!</h2>
<p>Obsah pre krok 8...</p> <p>Gratulujeme! Váš nový web bol úspešne vygenerovaný a je pripravený na stiahnutie.</p>
<div class="export-actions">
<button id="btn-download-zip" class="btn-primary btn-large">Stiahnuť web (ZIP)</button>
<div style="margin-top: 1rem;">
<button id="btn-view-live" class="btn-secondary btn-small">👁️ Pozrieť LIVE v novom okne</button>
</div>
</div>
<div class="instructions-box">
<h3>Čo robiť po stiahnutí?</h3>
<ol>
<li>Rozbaľte stiahnutý .zip archív vo vašom počítači.</li>
<li>Nahrajte všetky súbory a priečinky na váš webhosting cez FTP.</li>
<li>Váš web bude okamžite funkčný na vašej doméne.</li>
<li>Ak ste zvolili <strong>Lokálny mód</strong> formulára, správy nájdete na adrese <code>/form-viewer.php</code>.</li>
</ol>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -273,6 +273,24 @@ const App = {
document.getElementById('config-smtp').classList.toggle('hidden', e.target.value !== 'smtp'); 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() { renderCategories() {
@ -534,9 +552,48 @@ const App = {
} }
this.state.currentStep = n; this.state.currentStep = n;
// Specific step logic
if (n === 7) {
this.loadPreview();
}
this.updateUI(); 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() { async nextStep() {
if (this.state.currentStep === 1) { if (this.state.currentStep === 1) {
if (!this.state.selection.category || !this.state.selection.subcategory) { if (!this.state.selection.category || !this.state.selection.subcategory) {

View File

@ -257,4 +257,55 @@ class ProjectActions
'mime_type' => $mimeType '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
];
}
} }