Initial PHP rebuild of the app
This commit is contained in:
690 files changed
+33944
-12046
No files matched your search
@@ -0,0 +1,25 @@
|
||||
<IfModule mod_rewrite.c>
|
||||
<IfModule mod_negotiation.c>
|
||||
Options -MultiViews -Indexes
|
||||
</IfModule>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# Handle Authorization Header
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
# Handle X-XSRF-Token Header
|
||||
RewriteCond %{HTTP:x-xsrf-token} .
|
||||
RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]
|
||||
|
||||
# Redirect Trailing Slashes If Not A Folder...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} (.+)/$
|
||||
RewriteRule ^ %1 [L,R=301]
|
||||
|
||||
# Send Requests To Front Controller...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L]
|
||||
</IfModule>
|
||||
Whitespace-only changes.
Binary file not shown.
|
Before Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
// Determine if the application is in maintenance mode...
|
||||
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
|
||||
require $maintenance;
|
||||
}
|
||||
|
||||
// Register the Composer autoloader...
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
// Bootstrap Laravel and handle the request...
|
||||
/** @var Application $app */
|
||||
$app = require_once __DIR__.'/../bootstrap/app.php';
|
||||
|
||||
$app->handleRequest(Request::capture());
|
||||
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
// Service worker — internal filename/comment only, never shown to a user.
|
||||
//
|
||||
// Scope, deliberately: this app is almost entirely dynamic, signed-in,
|
||||
// per-request server-rendered content (appointments, calendars, loyalty
|
||||
// balances) — caching that would risk serving stale or, worse, another
|
||||
// user's data. So this file does NOT try to make the booking flow work
|
||||
// offline. What it does do:
|
||||
// - Push notifications (`push`/`notificationclick` — unchanged from the
|
||||
// original Phase 7 push-notifications work).
|
||||
// - Precache + serve a small static app shell (offline fallback page,
|
||||
// manifest, icons) so a lost connection shows a real "you're offline"
|
||||
// screen instead of the browser's own dinosaur/error page.
|
||||
// - An update-available handshake: a new SW version installs and waits
|
||||
// rather than taking over immediately, so the running page can prompt
|
||||
// the user before switching (see update-available-banner.tsx).
|
||||
//
|
||||
// Every GET request that isn't the app shell or a navigation falls
|
||||
// through untouched (`fetch` handler doesn't call respondWith), and every
|
||||
// non-GET request (Server Actions, API mutations) is never intercepted at
|
||||
// all — this worker must never sit between the app and a write.
|
||||
|
||||
// v2: /manifest.webmanifest was previously cache-first here alongside the
|
||||
// truly static icon fallbacks — but it's dynamic, per-business content
|
||||
// (name/icons/colors, editable from "App Icon & Home Screen" settings),
|
||||
// not a static build asset. Cache-first meant a phone that had ever
|
||||
// loaded it once would keep serving that stale response indefinitely,
|
||||
// regardless of any later branding change or server-side fix — bumping
|
||||
// CACHE_VERSION forces every existing install to drop its old cache
|
||||
// (including any previously-cached manifest response) on next activate.
|
||||
const CACHE_VERSION = "v2";
|
||||
const CACHE_NAME = `nextchair-shell-${CACHE_VERSION}`;
|
||||
const OFFLINE_URL = "/offline";
|
||||
|
||||
const APP_SHELL = [OFFLINE_URL, "/icons/icon-192.png", "/icons/icon-512.png"];
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
||||
caches
|
||||
.open(CACHE_NAME)
|
||||
.then((cache) => cache.addAll(APP_SHELL))
|
||||
// Deliberately no self.skipWaiting() here — an already-open tab keeps
|
||||
// running its current version until the user acts on the
|
||||
// update-available prompt (see the `message` handler below).
|
||||
.catch(() => {
|
||||
// A failed precache (e.g. first install with no network at all)
|
||||
// shouldn't block installation — push notifications should still
|
||||
// work even if the offline shell couldn't be cached yet.
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
caches
|
||||
.keys()
|
||||
.then((keys) => Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))))
|
||||
.then(() => self.clients.claim()),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
const { request } = event;
|
||||
if (request.method !== "GET") return;
|
||||
|
||||
const url = new URL(request.url);
|
||||
if (url.origin !== self.location.origin) return;
|
||||
|
||||
// Page navigations: network-first (this app is dynamic — a cached page
|
||||
// would be stale or session-mismatched), falling back to the offline
|
||||
// shell only when the network is actually unreachable.
|
||||
if (request.mode === "navigate") {
|
||||
event.respondWith(
|
||||
fetch(request).catch(() => caches.match(OFFLINE_URL).then((cached) => cached ?? Response.error())),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// The static app-shell assets themselves: cache-first, since these are
|
||||
// versioned by CACHE_NAME and only change on a new SW deploy.
|
||||
if (APP_SHELL.some((path) => url.pathname === path)) {
|
||||
event.respondWith(
|
||||
caches.match(request).then((cached) => cached ?? fetch(request)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything else (Next static build assets, API routes, images) —
|
||||
// untouched, normal network fetch.
|
||||
});
|
||||
|
||||
self.addEventListener("message", (event) => {
|
||||
if (event.data?.type === "SKIP_WAITING") self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener("push", (event) => {
|
||||
if (!event.data) return;
|
||||
|
||||
let payload;
|
||||
try {
|
||||
payload = event.data.json();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
// The server always sets a real, business-branded title (see
|
||||
// src/services/notifications/reminder-worker.ts) — this is only a
|
||||
// defensive fallback for a malformed payload, so it stays neutral
|
||||
// rather than naming the internal product.
|
||||
const title = payload.title || "New notification";
|
||||
const options = {
|
||||
body: payload.body || "",
|
||||
data: { url: payload.url || "/" },
|
||||
icon: "/icons/icon-192.png",
|
||||
badge: "/icons/icon-192.png",
|
||||
};
|
||||
|
||||
event.waitUntil(self.registration.showNotification(title, options));
|
||||
});
|
||||
|
||||
self.addEventListener("notificationclick", (event) => {
|
||||
event.notification.close();
|
||||
const url = event.notification.data?.url || "/";
|
||||
|
||||
event.waitUntil(
|
||||
self.clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
|
||||
for (const client of clientList) {
|
||||
if (client.url.includes(url) && "focus" in client) return client.focus();
|
||||
}
|
||||
if (self.clients.openWindow) return self.clients.openWindow(url);
|
||||
}),
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user