FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index mod_harbour PWA Template - Agenda
Posts: 6983
Joined: Fri Oct 07, 2005 07:07 PM
PWA Template - Agenda
Posted: Sat Jan 03, 2026 08:24 PM

What We Optimized Today—and Why It’s Now Your Perfect PWA Template

Core Message: From a functional CRUD test page, we’ve crafted a lean, Mobile-First PWA template—free from external dependencies, with clean UX and maximum reusability for your projects.


---

Key Improvements in Detail

  1. Fixed Navigation (from sticky to fixed)
       Problem: On iOS/Safari/PWA, position: sticky (especially combined with Safe Area and blur effects) can unpredictably “break” during scrolling.
       Solution: The header now behaves like a native app—stable, always visible, and jump-free.

  2. Dynamic Header Height
       Why? To ensure content starts precisely below the navigation bar on any device, the header height is measured via JavaScript and set as a CSS variable.
       Result: Pixel-perfect layout, regardless of device or browser.

  3. Optimized Input Fields
       Mobile-First: Compact height but larger font for better readability and usability—delivering a true “app-like” feel.

  4. Smart Editing Behavior
       UX Boost:
       - The last edited record automatically moves to position 1 and is highlighted after reload.
       - Scroll offset is calculated to prevent content from disappearing under the header.

  5. Dual-Function Refresh Button
       - Tap: Soft refresh (reloads data + checks for Service Worker updates).
       - Long-press: Hard refresh (clears cache/SW + full reload).
       Especially useful: Ideal for developers testing PWA updates or users who want manual control.

  6. “Scroll to Top” Button
       Practical for long lists: Appears only after scrolling and returns users to the top with a single tap.


---

Why This Template Speeds Up Your Projects

✅ GDPR-compliant: No external assets, no CDN, no tracking—100% privacy-friendly.
âś… PWA-ready: Built-in manifest, icons, Service Worker, and a thoughtful update workflow.
âś… Mobile-First UX:
   - Safe Area support,
   - optimized touch targets,
   - clear actions,
   - modern card-based lists.
âś… Template-ready:
   - Clean, modular code,
   - event delegation,
   - clear API layer (list/get/save/delete).
   Ready for any CRUD application (customers, items, notes, tickets, room planning tools, etc.).
✅ iOS-safe: The fixed header avoids typical sticky issues—no more annoying disappearing acts.


---

Conclusion: A template that works, scales, and feels like a native app—without bloat, with a focus on performance and user experience.

A proxy solution (browser → PHP proxy → microservice/DBF server) gives you these practical advantages—especially in your PWA + microservice setup:

Security: one central gatekeeper
The browser only talks to the proxy (same domain). The proxy can enforce: tokens, allow-lists (actions/paths), rate limits, input validation. The microservice can stay “lean” and internal.

No CORS / mixed-content headaches
Everything is same-origin, so you avoid CORS headers, preflight requests, and iOS/PWA edge cases.

Central validation & sanitizing
Normalize/validate inputs (field types, lengths, allowed values) and block dangerous parameters before they ever reach the DBF server.

Audit logging in one place
Consistent logs of who/when/what—without spreading logging logic across every microservice route.

Hide internal architecture
The client never sees ports, hostnames, or internal routes/error details. You can change microservice endpoints later without breaking the frontend.

Stability & performance controls
The proxy can handle timeouts cleanly, standardize JSON error responses, and optionally cache safe calls like list for short periods.

A repeatable pattern for all apps
Agenda, room planner, DMS, xbrowse—same “front door” (*_proxy.php) everywhere. That consistency makes everything easier to maintain.

<?php

/* ==================================================
   BASIC HEADERS
================================================== */
header('Content-Type: application/json; charset=utf-8');

/* ==================================================
   PROXY-SELFSCHUTZ (minimal & ausreichend)
   → verhindert Direktaufruf aus dem Internet
================================================== */
$origin  = $_SERVER['HTTP_ORIGIN']  ?? '';
$referer = $_SERVER['HTTP_REFERER'] ?? '';

$allowedOrigins = [
  'http://localhost',
  'http://127.0.0.1',
  'https://demo.space',
  'https://www.demo.space'
];


$allowed = false;
foreach ($allowedOrigins as $a) {
    if (strpos($origin, $a) === 0 || strpos($referer, $a) === 0) {
        $allowed = true;
        break;
    }
}

if (!$allowed) {
    http_response_code(403);
    echo json_encode([
        'success' => false,
        'message' => 'PROXY_FORBIDDEN'
    ], JSON_UNESCAPED_UNICODE);
    exit;
}

/* ==================================================
   RAW INPUT
================================================== */
$inputRaw = file_get_contents('php://input');
if ($inputRaw === false || $inputRaw === '') {
    $inputRaw = '{}';
}

/* ==================================================
   MICROservice URL
================================================== */
$msUrl = 'http://127.0.0.1:9090/agenda';

/* ==================================================
   SERVICE TOKEN (Bearer)
================================================== */
$token = getenv('GS_TOKEN');
if (!$token) {
    http_response_code(500);
    echo json_encode([
        'success' => false,
        'message' => 'GS_TOKEN_NOT_SET'
    ], JSON_UNESCAPED_UNICODE);
    exit;
}

/* ==================================================
   STREAM CONTEXT
================================================== */
$headers  = "Content-Type: application/json\r\n";
$headers .= "Authorization: Bearer {$token}\r\n";
$headers .= "Connection: close\r\n";

$ctx = stream_context_create([
    'http' => [
        'method'        => 'POST',
        'header'        => $headers,
        'content'       => $inputRaw,
        'timeout'       => 300,
        'ignore_errors' => true   // wichtig: wir wollen auch 401/500 lesen
    ]
]);

/* ==================================================
   CONNECT
================================================== */
$T0 = hrtime(true);
$fp = @fopen($msUrl, 'rb', false, $ctx);

if (!$fp) {
    http_response_code(502);
    echo json_encode([
        'success' => false,
        'message' => 'CONNECT_FAILED',
        'error'   => error_get_last()['message'] ?? 'unknown'
    ], JSON_UNESCAPED_UNICODE);
    exit;
}

/* ==================================================
   HTTP STATUS AUS MICROSERVICE ĂśBERNEHMEN
================================================== */
$statusLine = $GLOBALS['http_response_header'][0] ?? '';
if (preg_match('~HTTP/\S+\s+(\d{3})~', $statusLine, $m)) {
    http_response_code((int)$m[1]);
}

/* ==================================================
   STREAM RESPONSE (transparent)
================================================== */
while (!feof($fp)) {
    $chunk = fread($fp, 8192);
    if ($chunk === false) break;
    if ($chunk !== '') {
        echo $chunk;
        @flush();
    }
}

fclose($fp);

/* ==================================================
   OPTIONAL: Timing (nur intern nutzbar)
================================================== */
$ms = round((hrtime(true) - $T0) / 1e6, 2);
// hier NICHT ausgeben – nur loggen, falls gewünscht

Continue the discussion