diff --git a/admin.php b/admin.php index 947717b..f40ef0a 100644 --- a/admin.php +++ b/admin.php @@ -3,6 +3,10 @@ declare(strict_types=1); require_once __DIR__ . '/urban_hikes.php'; $storage = urban_hikes_storage(); +$routeId = isset($_GET['id']) ? (int)$_GET['id'] : 0; +$editingRoute = $routeId > 0 ? urban_hikes_find($routeId) : null; +$isEditMode = $editingRoute !== null; +$missingRoute = $routeId > 0 && !$editingRoute; $errors = []; $formData = [ 'city' => '', @@ -18,21 +22,44 @@ $formData = [ 'best_for' => '', ]; +if ($editingRoute) { + $formData = array_merge($formData, $editingRoute); +} + if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $result = urban_hikes_create($_POST); + $postedId = isset($_POST['route_id']) ? (int)$_POST['route_id'] : 0; + if ($postedId > 0) { + $result = urban_hikes_update($postedId, $_POST); + $routeId = $postedId; + $isEditMode = true; + } else { + $result = urban_hikes_create($_POST); + $routeId = 0; + $isEditMode = false; + } + if (!empty($result['success'])) { - urban_hikes_set_flash('success', 'Route saved and published to the directory.'); + urban_hikes_set_flash('success', $postedId > 0 ? 'Route updated successfully.' : 'Route saved and published to the directory.'); header('Location: route.php?id=' . (int)$result['id']); exit; } $errors = $result['errors'] ?? []; $formData = array_merge($formData, $result['input'] ?? []); + $editingRoute = $routeId > 0 ? urban_hikes_find($routeId) : null; + $missingRoute = $routeId > 0 && !$editingRoute; } -$latestRoutes = urban_hikes_latest(); -$pageTitle = 'Add a route | ' . urban_hikes_project_name(); -$pageDescription = 'Add an urban hiking route with city, difficulty, distance, highlights, and map link.'; +if ($missingRoute) { + http_response_code(404); +} + +$latestRoutes = urban_hikes_latest(10); +$pageTitle = ($isEditMode ? 'Edit route' : 'Add a route') . ' | ' . urban_hikes_project_name(); +$pageDescription = $isEditMode + ? 'Update an existing urban hiking route and republish the latest details.' + : 'Add an urban hiking route with city, difficulty, distance, highlights, and map link.'; +$submitLabel = $isEditMode ? 'Save changes' : 'Publish route'; urban_hikes_render_head($pageTitle, $pageDescription, 'noindex, follow'); urban_hikes_render_nav('admin'); @@ -40,147 +67,182 @@ urban_hikes_render_nav('admin');
-
-
-
- Content admin -

Add a new urban route

-

This first admin screen keeps the workflow small: add a route once, then browse it in the public directory immediately.

- - - - - - - - - -
-
- - -
-
-
- - -
-
-
- - -
-
-
- - -
-
-
- - -
-
-
- - -
-
-
- - -
-
-
- - -
-
-
- - -
Enter one stop or viewpoint per line.
-
-
-
- - -
-
-
- - -
-
-
- - Back to directory -
-
+ +
+ Route missing +

That route could not be opened for editing.

+

Try returning to the directory and opening a valid route first.

+
+ +
+
+
+ Content admin +

+

+ +

-
-
- Workflow -

What this first slice covers

-
    -
  • Add a route with the key planning fields
  • -
  • Store it in MariaDB with prepared statements
  • -
  • Redirect to a detail page with a confirmation toast
  • -
  • See it in the public directory immediately
  • -
-
-
-
-
- Latest entries -

Recent routes

-
- Open directory -
- -

No routes published yet.

- -
- - - - - - - - - - - - - - - + + + + + + + + + + + + +
+ + +
+
+
+ +
-
RouteCityView
-
-
· km
-
Open
+ +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
Enter one stop or viewpoint per line.
+
+
+
+ + +
+
+
+ + +
+
+
+ + + Cancel + + Back to directory + +
+ +
+
+ +
+
+ +

+
    + +
  • Update route content without touching the database manually
  • +
  • Keep validation and safe prepared statements in one place
  • +
  • Return to the public route page after saving
  • +
  • Refresh the route everywhere it appears in the directory
  • + +
  • Add a route with the key planning fields
  • +
  • Store it in MariaDB with prepared statements
  • +
  • Redirect to a detail page with a confirmation toast
  • +
  • See it in the public directory immediately
  • + +
+
+
+
+
+ Manage routes +

Recent routes

+
+ Open directory
- + +

No routes published yet.

+ +
+ + + + + + + + + + + + + + + + + +
RouteCityActions
+
+
· km
+
+
+ Edit + Open +
+
+
+ +
-
+
diff --git a/city.php b/city.php new file mode 100644 index 0000000..21d0e84 --- /dev/null +++ b/city.php @@ -0,0 +1,191 @@ + +
+
+
+ +
+ City missing +

We could not find that city guide.

+

Try opening a city from the directory instead.

+ Back to cities +
+ + + +
+
+ City guide +

Urban hiking in

+

+ Use this city page as your quick planner: compare route length, difficulty, starting point, and route highlights before heading out. +

+ +
+
+
+
+
+

Routes

+

+
+
+

Avg km

+

+
+
+

Avg hours

+

+
+
+

Areas

+

+
+
+
+
    +
  • easy · moderate · challenging
  • + +
  • Shortest option: at km
  • + + +
  • Longest option: at km
  • + +
+
+
+
+ +
+
+ + +
+
+
+
+
+
+ routes +

Pick the walk that fits your day.

+

Every route below links to a full detail page and an external map.

+
+ Add a route +
+ +
+ + +
+ +
+ +
+
+ +
+
+ Planning notes +

How to use this city page

+
    +
  • Start with distance and duration to match your available time.
  • +
  • Open a route map only after the route summary looks right.
  • +
  • Use the filtered directory view if you want broader search controls.
  • +
+
+
+
+
+ Explore more +

Other city guides

+
+ All cities +
+ +

Add another city route to expand the guide.

+ + + +
+
+
+
+
+ +
+ diff --git a/index.php b/index.php index 0305718..e76ce34 100644 --- a/index.php +++ b/index.php @@ -173,6 +173,7 @@ urban_hikes_render_nav('directory');
View details + City guide Open map
@@ -198,7 +199,7 @@ urban_hikes_render_nav('directory');
- + route diff --git a/route.php b/route.php index 664eb58..e6c3a60 100644 --- a/route.php +++ b/route.php @@ -42,7 +42,7 @@ urban_hikes_render_nav(); @@ -105,7 +105,11 @@ urban_hikes_render_nav(); Next move

Want to add another city route?

Use the lightweight admin screen to submit a new urban hike and make it searchable right away.

- Add a route +
More in diff --git a/urban_hikes.php b/urban_hikes.php index 492cb9b..96bdf70 100644 --- a/urban_hikes.php +++ b/urban_hikes.php @@ -282,6 +282,33 @@ function urban_hikes_find(int $id): ?array return $route ?: null; } +function urban_hikes_find_by_city(string $city): array +{ + $storage = urban_hikes_storage(); + if (!$storage['ready'] || $city === '') { + return []; + } + + $stmt = $storage['pdo']->prepare('SELECT * FROM urban_routes WHERE city = :city ORDER BY distance_km ASC, title ASC'); + $stmt->execute([':city' => $city]); + return $stmt->fetchAll(); +} + +function urban_hikes_city_stats(string $city): ?array +{ + $storage = urban_hikes_storage(); + if (!$storage['ready'] || $city === '') { + return null; + } + + $sql = 'SELECT city, COUNT(*) AS route_count, AVG(distance_km) AS avg_distance, AVG(duration_hours) AS avg_duration, COUNT(DISTINCT neighborhood) AS area_count, SUM(CASE WHEN difficulty = "Easy" THEN 1 ELSE 0 END) AS easy_count, SUM(CASE WHEN difficulty = "Moderate" THEN 1 ELSE 0 END) AS moderate_count, SUM(CASE WHEN difficulty = "Challenging" THEN 1 ELSE 0 END) AS challenging_count FROM urban_routes WHERE city = :city GROUP BY city LIMIT 1'; + $stmt = $storage['pdo']->prepare($sql); + $stmt->execute([':city' => $city]); + $stats = $stmt->fetch(); + + return $stats ?: null; +} + function urban_hikes_related(string $city, int $excludeId, int $limit = 3): array { $storage = urban_hikes_storage(); @@ -393,6 +420,45 @@ function urban_hikes_create(array $input): array return ['success' => true, 'id' => (int)$storage['pdo']->lastInsertId()]; } +function urban_hikes_update(int $id, array $input): array +{ + $storage = urban_hikes_storage(); + [$clean, $errors] = urban_hikes_validation($input); + + if (!$storage['ready']) { + return ['success' => false, 'errors' => ['storage' => 'Database is currently unavailable.'], 'input' => $clean]; + } + + $existing = urban_hikes_find($id); + if (!$existing) { + return ['success' => false, 'errors' => ['storage' => 'That route could not be found.'], 'input' => $clean]; + } + + if ($errors) { + return ['success' => false, 'errors' => $errors, 'input' => $clean]; + } + + $slug = $existing['title'] === $clean['title'] ? (string)$existing['slug'] : urban_hikes_unique_slug($storage['pdo'], $clean['title']); + $stmt = $storage['pdo']->prepare('UPDATE urban_routes SET city = :city, title = :title, slug = :slug, summary = :summary, distance_km = :distance_km, duration_hours = :duration_hours, difficulty = :difficulty, neighborhood = :neighborhood, start_point = :start_point, highlights = :highlights, map_url = :map_url, best_for = :best_for WHERE id = :id LIMIT 1'); + $stmt->execute([ + ':city' => $clean['city'], + ':title' => $clean['title'], + ':slug' => $slug, + ':summary' => $clean['summary'], + ':distance_km' => round((float)$clean['distance_km'], 1), + ':duration_hours' => round((float)$clean['duration_hours'], 1), + ':difficulty' => $clean['difficulty'], + ':neighborhood' => $clean['neighborhood'], + ':start_point' => $clean['start_point'], + ':highlights' => $clean['highlights'], + ':map_url' => $clean['map_url'], + ':best_for' => $clean['best_for'], + ':id' => $id, + ]); + + return ['success' => true, 'id' => $id]; +} + function urban_hikes_highlight_items(string $value): array { $items = preg_split('/\r\n|\r|\n/', $value) ?: [];