Compare commits

...

9 Commits

Author SHA1 Message Date
Flatlogic Bot
5ad904b265 Auto commit: 2025-12-02T15:20:58.075Z 2025-12-02 15:20:58 +00:00
Flatlogic Bot
e06d66bb12 Auto commit: 2025-12-02T15:04:45.684Z 2025-12-02 15:04:45 +00:00
Flatlogic Bot
3c98edbcb2 Auto commit: 2025-12-02T14:46:23.406Z 2025-12-02 14:46:23 +00:00
Flatlogic Bot
1f5994e0b6 Edit index.php via Editor 2025-12-02 14:32:30 +00:00
Flatlogic Bot
d7e994182c Edit index.php via Editor 2025-12-02 14:32:16 +00:00
Flatlogic Bot
467127b834 Revert to version a5e399e 2025-12-02 14:31:12 +00:00
Flatlogic Bot
8bbf937b65 Revert to version 503a892 2025-12-02 14:30:33 +00:00
Flatlogic Bot
a5e399e852 Auto commit: 2025-12-02T14:30:16.367Z 2025-12-02 14:30:16 +00:00
Flatlogic Bot
503a892933 Auto commit: 2025-12-02T14:17:38.474Z 2025-12-02 14:17:38 +00:00
7 changed files with 778 additions and 146 deletions

131
api/index.php Normal file
View File

@ -0,0 +1,131 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
header('Content-Type: application/json');
require_once __DIR__ . '/../ai/LocalAIApi.php';
require_once __DIR__ . '/../includes/pexels.php';
require_once __DIR__ . '/../db/config.php';
$action = $_GET['action'] ?? 'get_suggestion';
switch ($action) {
case 'get_suggestion':
handle_get_suggestion();
break;
case 'save_suggestion':
handle_save_suggestion();
break;
default:
echo json_encode(['error' => 'Invalid action']);
break;
}
function handle_get_suggestion() {
$requestBody = file_get_contents('php://input');
$data = json_decode($requestBody, true);
$query = $data['query'] ?? '';
$persona = $data['persona'] ?? 'travel_agent';
$conversation = $data['conversation'] ?? [];
if (empty($query)) {
echo json_encode(['error' => 'Query is empty']);
exit;
}
$conversation[] = ['role' => 'user', 'content' => $query];
$persona_prompts = [
'travel_agent' => 'You are a helpful travel agent specializing in Poland.',
'historian' => 'You are a historian specializing in Polish history. Your responses should be informative, detailed, and focus on the historical significance of places and events.',
'foodie' => 'You are a food critic and blogger with a passion for Polish cuisine. Your responses should be enthusiastic, descriptive, and focus on food, restaurants, and culinary experiences.',
'adventurer' => 'You are an adventure travel guide who loves exploring the wild side of Poland. Your responses should be exciting, energetic, and focus on outdoor activities, hiking, and unique experiences.'
];
$system_prompt = $persona_prompts[$persona] . ' Provide a travel suggestion with a title, a short description, a 3-day itinerary, and the latitude and longitude of the location. Your response must be in JSON format, like this: {"title": "...", "description": "...", "itinerary": [{"day": 1, "activities": ["...", "..."]}, {"day": 2, "activities": ["...", "..."]}, {"day": 3, "activities": ["...", "..."]}], "location": {"lat": ..., "lng": ...}}. The user is on a conversation, so answer the last query based on the context of the conversation.';
$messages = array_merge([['role' => 'system', 'content' => $system_prompt]], $conversation);
$resp = LocalAIApi::createResponse(
[
'input' => $messages
]
);
if (!empty($resp['success'])) {
$text = LocalAIApi::extractText($resp);
$aiResponse = json_decode($text, true);
if (json_last_error() !== JSON_ERROR_NONE) {
preg_match('/"title":\s*"(.*?)"/', $text, $title_matches);
preg_match('/"description":\s*"(.*?)"/', $text, $description_matches);
$title = $title_matches[1] ?? 'AI Generated Response';
$description = $description_matches[1] ?? $text;
$itinerary = [];
$location = null;
} else {
$title = $aiResponse['title'] ?? 'AI Generated Response';
$description = $aiResponse['description'] ?? 'No description available.';
$itinerary = $aiResponse['itinerary'] ?? [];
$location = $aiResponse['location'] ?? null;
}
$pexelsUrl = 'https://api.pexels.com/v1/search?query=' . urlencode($title) . '&orientation=landscape&per_page=1&page=1';
$pexelsData = pexels_get($pexelsUrl);
$imageUrl = 'https://images.pexels.com/photos/1699030/pexels-photo-1699030.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1';
if ($pexelsData && !empty($pexelsData['photos'])) {
$photo = $pexelsData['photos'][0];
$imageUrl = $photo['src']['large2x'] ?? ($photo['src']['large'] ?? $photo['src']['original']);
}
echo json_encode([
'title' => $title,
'description' => $description,
'itinerary' => $itinerary,
'location' => $location,
'image' => $imageUrl
]);
} else {
error_log('AI error: ' . ($resp['error'] ?? 'unknown'));
echo json_encode(['error' => 'Failed to get AI response']);
}
}
function handle_save_suggestion() {
$requestBody = file_get_contents('php://input');
$data = json_decode($requestBody, true);
$title = $data['title'] ?? null;
$description = $data['description'] ?? null;
$itinerary = $data['itinerary'] ?? null;
$location = $data['location'] ?? null;
$image_url = $data['image'] ?? null;
if (empty($title)) {
echo json_encode(['error' => 'Title is required to save a suggestion.']);
exit;
}
try {
$pdo = db();
$stmt = $pdo->prepare("
INSERT INTO saved_suggestions (title, description, itinerary, location, image_url)
VALUES (:title, :description, :itinerary, :location, :image_url)
");
$stmt->execute([
':title' => $title,
':description' => $description,
':itinerary' => json_encode($itinerary),
':location' => json_encode($location),
':image_url' => $image_url
]);
echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]);
} catch (PDOException $e) {
error_log('DB error: ' . $e->getMessage());
echo json_encode(['error' => 'Failed to save suggestion to the database.']);
}
}

124
assets/css/custom.css Normal file
View File

@ -0,0 +1,124 @@
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap');
body {
background-color: #f8f9fa;
font-family: 'Poppins', sans-serif;
color: #333;
}
.hero {
background: linear-gradient(135deg, #6D28D9, #DB2777);
color: white;
padding: 6rem 2rem;
text-align: center;
position: relative;
overflow: hidden;
border-bottom-left-radius: 50% 20%;
border-bottom-right-radius: 50% 20%;
}
.hero h1 {
font-size: 3.5rem;
font-weight: 700;
}
.card {
border-radius: 1rem;
border: none;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
transition: transform 0.2s ease-in-out;
}
.card:hover {
transform: translateY(-5px);
}
.btn-primary {
background-color: #DB2777;
border-color: #DB2777;
border-radius: 0.75rem;
padding: 0.75rem 2rem;
font-weight: 600;
transition: background-color 0.2s ease;
}
.btn-primary:hover {
background-color: #c41a68;
border-color: #c41a68;
}
.form-control {
border-radius: 0.75rem;
padding: 0.75rem 1.5rem;
background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(255, 255, 255, 0.2);
}
#suggestion-card {
display: none;
}
#loading {
display: none;
}
.spinner-border {
color: #DB2777 !important;
}
#chat-container {
max-height: 600px;
overflow-y: auto;
padding: 1rem;
background-color: #fff;
border-radius: 1rem;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.message-wrapper {
display: flex;
margin-bottom: 1rem;
}
.user-wrapper {
justify-content: flex-end;
}
.assistant-wrapper {
justify-content: flex-start;
}
.message-bubble {
max-width: 80%;
padding: 1rem;
border-radius: 1rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.user-bubble {
background-color: #DB2777;
color: white;
border-top-right-radius: 0;
}
.assistant-bubble {
background-color: #f1f0f0;
border-top-left-radius: 0;
}
.assistant-bubble .card-body {
padding: 0;
}
.assistant-bubble img {
border-top-left-radius: 1rem;
border-top-right-radius: 1rem;
max-width: 100%;
}
.itinerary-section {
margin-top: 1rem;
}

237
assets/js/main.js Normal file
View File

@ -0,0 +1,237 @@
document.addEventListener('DOMContentLoaded', () => {
const suggestionForm = document.getElementById('suggestion-form');
const queryInput = document.getElementById('query');
const personaSelect = document.getElementById('persona');
const loadingIndicator = document.getElementById('loading');
const errorMessage = document.getElementById('error-message');
const errorText = document.getElementById('error-text');
const chatContainer = document.getElementById('chat-container');
const luckyButton = document.getElementById('lucky-button');
const funFact = document.getElementById('fun-fact');
let conversation = [];
let map;
let funFactInterval;
const funFacts = [
"Poland is home to the world's largest castle, Malbork Castle.",
"The name \"Poland\" (Polska) originates from the Polanie tribe, meaning \"people living in open fields.\"",
"Poland has 17 Nobel Prize winners, including four for Peace and five for Literature.",
"The Polish alphabet consists of 32 letters.",
"Marie Curie, the pioneering scientist, was Polish.",
"Poland is the world's biggest exporter of amber.",
"The city of Wrocław has a network of over 300 small bronze statues of dwarves (krasnale).",
"Poland's Białowieża Forest is one of the last and largest remaining parts of the immense primeval forest that once stretched across the European Plain."
];
suggestionForm.addEventListener('submit', function(e) {
e.preventDefault();
const query = queryInput.value.trim();
if (!query) return;
const persona = personaSelect.value;
const userMessage = { role: 'user', content: query };
conversation.push(userMessage);
appendMessage('user', { content: query });
queryInput.value = '';
fetchSuggestions(persona);
});
luckyButton.addEventListener('click', function() {
const queries = [
"a hidden gem in Warsaw",
"the best pierogi in Krakow",
"a beautiful beach on the Baltic coast",
"a challenging hike in the Tatra Mountains",
"a quirky museum in Gdansk"
];
const randomQuery = queries[Math.floor(Math.random() * queries.length)];
queryInput.value = randomQuery;
suggestionForm.dispatchEvent(new Event('submit'));
});
function showRandomFunFact() {
const randomIndex = Math.floor(Math.random() * funFacts.length);
funFact.textContent = funFacts[randomIndex];
}
function startLoading() {
loadingIndicator.style.display = 'block';
showRandomFunFact();
funFactInterval = setInterval(showRandomFunFact, 3000);
}
function stopLoading() {
loadingIndicator.style.display = 'none';
clearInterval(funFactInterval);
}
function showError(message) {
errorText.textContent = message;
errorMessage.style.display = 'block';
}
function hideError() {
errorMessage.style.display = 'none';
}
function appendMessage(role, data) {
const messageWrapper = document.createElement('div');
messageWrapper.classList.add('message-wrapper', `${role}-wrapper`);
const messageBubble = document.createElement('div');
messageBubble.classList.add('message-bubble', `${role}-bubble`);
if (role === 'user') {
messageBubble.textContent = data.content;
} else {
if (data.image) {
const img = document.createElement('img');
img.src = data.image;
img.classList.add('card-img-top');
messageBubble.appendChild(img);
}
const cardBody = document.createElement('div');
cardBody.classList.add('card-body');
if (data.title) {
const title = document.createElement('h5');
title.classList.add('card-title');
title.textContent = data.title;
cardBody.appendChild(title);
}
if (data.description) {
const description = document.createElement('p');
description.classList.add('card-text');
description.innerHTML = data.description; // Using innerHTML to render line breaks
cardBody.appendChild(description);
}
if (data.itinerary && data.itinerary.length > 0) {
const itinerarySection = document.createElement('div');
itinerarySection.classList.add('itinerary-section');
const itineraryTitle = document.createElement('h6');
itineraryTitle.textContent = 'Itinerary';
itinerarySection.appendChild(itineraryTitle);
const itineraryList = document.createElement('ul');
itineraryList.classList.add('list-group');
data.itinerary.forEach(day => {
const li = document.createElement('li');
li.classList.add('list-group-item');
li.innerHTML = `<b>Day ${day.day}:</b> ${day.activities.join(', ')}`;
itineraryList.appendChild(li);
});
itinerarySection.appendChild(itineraryList);
cardBody.appendChild(itinerarySection);
}
if (data.location && data.location.lat && data.location.lng) {
const mapId = `map-${Date.now()}`;
const mapDiv = document.createElement('div');
mapDiv.id = mapId;
mapDiv.style.height = '300px';
mapDiv.classList.add('mt-3');
cardBody.appendChild(mapDiv);
// Use a timeout to ensure the div is in the DOM before initializing the map
setTimeout(() => {
const map = L.map(mapId).setView([data.location.lat, data.location.lng], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
L.marker([data.location.lat, data.location.lng]).addTo(map)
.bindPopup(data.title || 'Location')
.openPopup();
}, 100);
}
messageBubble.appendChild(cardBody);
const saveButton = document.createElement('button');
saveButton.textContent = 'Save';
saveButton.classList.add('btn', 'btn-sm', 'btn-outline-primary', 'mt-2');
saveButton.addEventListener('click', () => saveSuggestion(data));
cardBody.appendChild(saveButton);
}
messageWrapper.appendChild(messageBubble);
chatContainer.appendChild(messageWrapper);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function saveSuggestion(suggestionData) {
fetch('api/index.php?action=save_suggestion', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(suggestionData)
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Suggestion saved!');
} else {
showError(data.error || 'Could not save suggestion.');
}
})
.catch(error => {
console.error('Error:', error);
showError('An error occurred while saving the suggestion.');
});
}
function fetchSuggestions(persona) {
startLoading();
hideError();
fetch('api/index.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: conversation[conversation.length - 1].content,
persona: persona,
conversation: conversation.slice(0, -1) // Send previous conversation
})
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
stopLoading();
if (data.error) {
showError(data.error);
// Do not add error to conversation history
return;
}
const assistantMessage = { role: 'assistant', content: data };
conversation.push(assistantMessage);
appendMessage('assistant', data);
})
.catch(error => {
console.error('Error:', error);
stopLoading();
showError('An error occurred while fetching the suggestion. Please try again.');
});
}
const errorAlert = document.getElementById('error-message');
if (errorAlert) {
errorAlert.querySelector('.btn-close').addEventListener('click', function () {
hideError();
});
}
});

21
db/migrate.php Normal file
View File

@ -0,0 +1,21 @@
<?php
require_once __DIR__ . '/config.php';
try {
$pdo = db();
$pdo->exec("
CREATE TABLE IF NOT EXISTS saved_suggestions (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
itinerary JSON,
location JSON,
image_url VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
");
echo "Migration successful!\n";
} catch (PDOException $e) {
die("Migration failed: " . $e->getMessage() . "\n");
}

26
includes/pexels.php Normal file
View File

@ -0,0 +1,26 @@
<?php
function pexels_key() {
$k = getenv('PEXELS_KEY');
return $k && strlen($k) > 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18';
}
function pexels_get($url) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ],
CURLOPT_TIMEOUT => 15,
]);
$resp = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code >= 200 && $code < 300 && $resp) return json_decode($resp, true);
return null;
}
function download_to($srcUrl, $destPath) {
$data = file_get_contents($srcUrl);
if ($data === false) return false;
if (!is_dir(dirname($destPath))) mkdir(dirname($destPath), 0775, true);
return file_put_contents($destPath, $data) !== false;
}

200
index.php
View File

@ -1,150 +1,66 @@
<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Style</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet">
<link href="assets/css/custom.css?v=<?php echo time(); ?>" rel="stylesheet">
</head>
<body>
<main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
<div class="hero">
<div class="container">
<h1>Your Next Adventure Awaits</h1>
<p class="lead">Discover Poland with AI.</p>
<a href="saved.php" class="btn btn-light">View Saved Suggestions</a>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
</div>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer>
<div class="container mt-n5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card p-4 shadow-lg">
<div class="card-body">
<form id="suggestion-form">
<div class="mb-4 text-center">
<label for="persona" class="form-label fs-5 mb-2">Choose your guide:</label>
<select class="form-select form-select-lg mb-3" id="persona">
<option value="travel_agent">Travel Agent</option>
<option value="historian">Historian</option>
<option value="foodie">Foodie</option>
<option value="adventurer">Adventurer</option>
</select>
<label for="query" class="form-label fs-4 mb-3">What are you looking for?</label>
<input type="text" class="form-control form-control-lg" id="query" placeholder="e.g., a cozy cafe in Krakow">
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary btn-lg px-5">Get Suggestion</button>
<button type="button" id="lucky-button" class="btn btn-secondary btn-lg px-5">I'm Feeling Lucky</button>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="row justify-content-center mt-4">
<div class="col-md-8">
<div id="error-message" class="alert alert-danger alert-dismissible fade show" role="alert" style="display: none;">
<span id="error-text"></span>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<div id="loading" class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p>Thinking...</p>
<p id="fun-fact" class="text-muted mt-2"></p>
</div>
<div id="chat-container" class="mt-4"></div>
</div>
</div>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

177
saved.php Normal file
View File

@ -0,0 +1,177 @@
<?php
require_once __DIR__ . '/db/config.php';
$pdo = db();
$single_suggestion = null;
if (isset($_GET['id'])) {
$stmt = $pdo->prepare("SELECT * FROM saved_suggestions WHERE id = ?");
$stmt->execute([$_GET['id']]);
$single_suggestion = $stmt->fetch();
}
$stmt = $pdo->query("SELECT * FROM saved_suggestions ORDER BY created_at DESC");
$suggestions = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Saved Suggestions</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<link href="assets/css/custom.css?v=<?php echo time(); ?>" rel="stylesheet">
<style>
.suggestion-card {
margin-bottom: 2rem;
}
</style>
</head>
<body>
<div class="hero">
<div class="container">
<h1><?php echo $single_suggestion ? htmlspecialchars($single_suggestion['title']) : 'Your Saved Adventures'; ?></h1>
<p class="lead"><?php echo $single_suggestion ? 'A shared travel idea' : 'Revisit your favorite Polish destinations.'; ?></p>
<a href="/" class="btn btn-light">Home</a>
</div>
</div>
<div class="container mt-5">
<div class="row <?php echo $single_suggestion ? 'justify-content-center' : ''; ?>">
<?php if ($single_suggestion): ?>
<div class="col-md-8">
<div class="card suggestion-card">
<img src="<?php echo htmlspecialchars($single_suggestion['image_url']); ?>" class="card-img-top" alt="<?php echo htmlspecialchars($single_suggestion['title']); ?>">
<div class="card-body">
<h5 class="card-title"><?php echo htmlspecialchars($single_suggestion['title']); ?></h5>
<p class="card-text"><?php echo nl2br(htmlspecialchars($single_suggestion['description'])); ?></p>
<?php
$itinerary = json_decode($single_suggestion['itinerary'], true);
if ($itinerary && !empty($itinerary)):
?>
<div class="itinerary-section">
<h6>Itinerary</h6>
<ul class="list-group">
<?php foreach ($itinerary as $day): ?>
<li class="list-group-item">
<b>Day <?php echo htmlspecialchars($day['day']); ?>:</b> <?php echo htmlspecialchars(implode(', ', $day['activities'])); ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php
$location = json_decode($single_suggestion['location'], true);
if ($location && !empty($location['lat']) && !empty($location['lng'])):
?>
<div id="map-single" style="height: 400px;" class="mt-3"></div>
<?php endif; ?>
</div>
</div>
</div>
<?php elseif (empty($suggestions)):
?>
<div class="col text-center">
<p>You haven't saved any suggestions yet.</p>
<a href="/" class="btn btn-primary">Find a new adventure</a>
</div>
<?php else: ?>
<?php foreach ($suggestions as $index => $suggestion): ?>
<div class="col-md-4">
<div class="card suggestion-card">
<img src="<?php echo htmlspecialchars($suggestion['image_url']); ?>" class="card-img-top" alt="<?php echo htmlspecialchars($suggestion['title']); ?>">
<div class="card-body">
<h5 class="card-title"><?php echo htmlspecialchars($suggestion['title']); ?></h5>
<p class="card-text"><?php echo nl2br(htmlspecialchars($suggestion['description'])); ?></p>
<?php
$itinerary = json_decode($suggestion['itinerary'], true);
if ($itinerary && !empty($itinerary)):
?>
<div class="itinerary-section">
<h6>Itinerary</h6>
<ul class="list-group">
<?php foreach ($itinerary as $day): ?>
<li class="list-group-item">
<b>Day <?php echo htmlspecialchars($day['day']); ?>:</b> <?php echo htmlspecialchars(implode(', ', $day['activities'])); ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php
$location = json_decode($suggestion['location'], true);
if ($location && !empty($location['lat']) && !empty($location['lng'])):
?>
<div id="map-<?php echo $index; ?>" style="height: 200px;" class="mt-3"></div>
<?php endif; ?>
<button class="btn btn-sm btn-outline-secondary mt-2 share-btn" data-id="<?php echo $suggestion['id']; ?>">Share</button>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
<?php if ($single_suggestion): ?>
<?php
$location = json_decode($single_suggestion['location'], true);
if ($location && !empty($location['lat']) && !empty($location['lng'])):
?>
var mapSingle = L.map('map-single').setView([<?php echo $location['lat']; ?>, <?php echo $location['lng']; ?>], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(mapSingle);
L.marker([<?php echo $location['lat']; ?>, <?php echo $location['lng']; ?>]).addTo(mapSingle)
.bindPopup(<?php echo json_encode($single_suggestion['title']); ?>)
.openPopup();
<?php endif; ?>
<?php else: ?>
<?php foreach ($suggestions as $index => $suggestion): ?>
<?php
$location = json_decode($suggestion['location'], true);
if ($location && !empty($location['lat']) && !empty($location['lng'])):
?>
var map<?php echo $index; ?> = L.map('map-<?php echo $index; ?>').setView([<?php echo $location['lat']; ?>, <?php echo $location['lng']; ?>], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map<?php echo $index; ?>);
L.marker([<?php echo $location['lat']; ?>, <?php echo $location['lng']; ?>]).addTo(map<?php echo $index; ?>)
.bindPopup(<?php echo json_encode($suggestion['title']); ?>)
.openPopup();
<?php endif; ?>
<?php endforeach; ?>
<?php endif; ?>
document.querySelectorAll('.share-btn').forEach(button => {
button.addEventListener('click', function() {
const suggestionId = this.dataset.id;
const shareUrl = `${window.location.origin}/saved.php?id=${suggestionId}`;
navigator.clipboard.writeText(shareUrl).then(() => {
this.textContent = 'Copied!';
this.disabled = true;
setTimeout(() => {
this.textContent = 'Share';
this.disabled = false;
}, 2000);
}).catch(err => {
console.error('Failed to copy: ', err);
});
});
});
});
</script>
</body>
</html>