PWA offline error page + login page cleanup (#327)

* Add friendly PWA offline error page

When the PWA fails to connect to the server, users now see a branded
offline page with a friendly "technical difficulties" message, the
app logo, and a reload button. The page automatically attempts to
reload when connectivity is restored.

Changes:
- Created public/offline.html with branded offline experience
- Updated service worker to cache and serve offline page on network failures
- Added service worker registration in application.js
- Service worker now handles navigation requests with offline fallback

* Extract PWA offline logo to separate cached asset

Move the inline SVG logo from offline.html to a separate file at
public/logo-offline.svg. This makes the logo asset easily identifiable
and maintainable, as it may diverge from other logo versions in the future.

Changes:
- Created public/logo-offline.svg with the offline page logo
- Updated service worker to cache logo as part of OFFLINE_ASSETS array
- Updated fetch handler to serve cached offline assets
- Updated offline.html to reference logo file instead of inline SVG

* Update offline message for better readability

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>

* CodeRabbit comments

* Keep 40x and 50x flowing

* Dark mode

* Logo tweaks

* Login/sign up cleanup

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Juan José Mata
2025-11-16 21:12:48 +01:00
committed by GitHub
parent 066fdf4ed5
commit d47aa2fe90
14 changed files with 208 additions and 21 deletions

View File

@@ -4,12 +4,9 @@
<div class="grow flex flex-col justify-center">
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<div class="flex justify-center mt-2 md:mb-6">
<%= image_tag "logo-color.png", class: "w-16 mb-6" %>
<%= image_tag "logomark.svg", class: "w-16 mb-6" %>
</div>
<div class="space-y-2">
<h2 class="text-3xl font-medium text-primary text-center">
<%= content_for?(:header_title) ? yield(:header_title).html_safe : t(".your_account") %>
</h2>
<% if (controller_name == "sessions" && action_name == "new") || (controller_name == "registrations" && action_name == "new") %>
<div class="space-y-3 md:hidden w-full my-4">
<div class="bg-surface-inset rounded-lg p-1 flex">

View File

@@ -1,10 +1,69 @@
const CACHE_VERSION = 'v1';
const OFFLINE_ASSETS = [
'/offline.html',
'/logo-offline.svg'
];
// Install event - cache the offline page and assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_VERSION).then((cache) => {
return cache.addAll(OFFLINE_ASSETS);
})
);
// Activate immediately
self.skipWaiting();
});
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_VERSION) {
return caches.delete(cacheName);
}
})
);
}).then(() => {
// Take control of all pages immediately
return self.clients.claim();
})
);
});
// Fetch event - serve offline page when network fails
self.addEventListener('fetch', (event) => {
// Handle navigation requests (page loads)
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch((error) => {
// Only show offline page for network errors
if (error.name === 'TypeError' || !navigator.onLine) {
return caches.match('/offline.html');
}
throw error;
})
);
}
// Handle offline assets (logo, etc.)
else if (OFFLINE_ASSETS.some(asset => new URL(event.request.url).pathname === asset)) {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
}
});
// Add a service worker for processing Web Push notifications:
//
// self.addEventListener("push", async (event) => {
// const { title, options } = await event.data.json()
// event.waitUntil(self.registration.showNotification(title, options))
// })
//
//
// self.addEventListener("notificationclick", function(event) {
// event.notification.close()
// event.waitUntil(
@@ -12,12 +71,12 @@
// for (let i = 0; i < clientList.length; i++) {
// let client = clientList[i]
// let clientPath = (new URL(client.url)).pathname
//
//
// if (clientPath == event.notification.data.path && "focus" in client) {
// return client.focus()
// }
// }
//
//
// if (clients.openWindow) {
// return clients.openWindow(event.notification.data.path)
// }

View File

@@ -1,7 +1,3 @@
<%
header_title t(".title")
%>
<% if @prefill_demo_credentials %>
<div class="mb-4 rounded-lg border bg-blue-50 text-blue-700 border-blue-200 theme-dark:bg-blue-900/20 theme-dark:text-blue-400 theme-dark:border-blue-800 p-4" role="status" aria-live="polite">
<div class="flex items-start gap-3">