From 00c40f2569bada79fde7629805b2f8da453dbd3e Mon Sep 17 00:00:00 2001 From: Julien Oculi Date: Wed, 17 Jul 2024 13:50:37 +0200 Subject: [PATCH] feat(pwa): :sparkles: add network status banner --- islands/IsOnline.css | 13 +++++++ islands/IsOnline.tsx | 52 ++++++++++++++++++++++++++ routes/_app.tsx | 4 ++ routes/api/serviceworker/is-online.tsx | 9 +++++ src/stylesheets/components.css | 1 + 5 files changed, 79 insertions(+) create mode 100644 islands/IsOnline.css create mode 100644 islands/IsOnline.tsx create mode 100644 routes/api/serviceworker/is-online.tsx diff --git a/islands/IsOnline.css b/islands/IsOnline.css new file mode 100644 index 0000000..11e31e1 --- /dev/null +++ b/islands/IsOnline.css @@ -0,0 +1,13 @@ +.island__is_online { + position: sticky; + top: 0; + width: 100%; + text-align: center; + padding: var(--_gap-half); + /* invert accent color */ + background: hsl(from #82c91e calc(h + 180) s l / 1); + font-family: var(--_font-family-accent); + color: white; + font-weight: bold; + z-index: 9999; +} diff --git a/islands/IsOnline.tsx b/islands/IsOnline.tsx new file mode 100644 index 0000000..5146069 --- /dev/null +++ b/islands/IsOnline.tsx @@ -0,0 +1,52 @@ +import { type Signal, useSignal } from '@preact/signals' +import { useEffect } from 'preact/hooks' +import { JSX } from 'preact' +import { requestApi } from ':src/utils.ts' + +type NetworkConnection = { + addEventListener: ( + type: 'change', + listener: (event: Event) => void | Promise, + ) => void +} + +export default function IsOnline( + { online, offline }: { online?: string; offline: string }, +) { + const status = useSignal(offline) + const connection = 'connection' in navigator + ? navigator.connection as NetworkConnection + : null + + useEffect(() => { + // Update connection status on network changes + connection?.addEventListener('change', () => { + updateNetworkStatus(status, online, offline) + }) + // Update connection status on 1st load + updateNetworkStatus(status, online, offline) + // Update connection status on interval (10s) + setInterval(() => updateNetworkStatus(status, online, offline), 10_000) + }) + + return <>{status} +} + +async function updateNetworkStatus( + status: Signal, + online: string | undefined, + offline: string, +) { + const isOnline = await requestApi( + '/serviceworker/is-online', + 'POST', + ).catch(() => false) + + if (isOnline) { + status.value = online === undefined + ? + : {online} + } else { + status.value = {offline} + } +} diff --git a/routes/_app.tsx b/routes/_app.tsx index 9b8dec6..0fe5565 100644 --- a/routes/_app.tsx +++ b/routes/_app.tsx @@ -3,6 +3,7 @@ import { type PageProps } from '$fresh/server.ts' import { Footer } from ':components/Footer.tsx' import { Header } from ':components/Header.tsx' import { ProgressiveWebApp } from ':components/ProgressiveWebApp.tsx' +import IsOnline from ':islands/IsOnline.tsx' export default function App({ Component }: PageProps) { return ( @@ -42,6 +43,9 @@ export default function App({ Component }: PageProps) {
+
diff --git a/routes/api/serviceworker/is-online.tsx b/routes/api/serviceworker/is-online.tsx new file mode 100644 index 0000000..35fb742 --- /dev/null +++ b/routes/api/serviceworker/is-online.tsx @@ -0,0 +1,9 @@ +import { SessionHandlers } from ':src/session/mod.ts' +import { respondApi } from ':src/utils.ts' + +export const handler: SessionHandlers = { + POST() { + // Check if server is online + return respondApi<'success', true>('success', true) + }, +} diff --git a/src/stylesheets/components.css b/src/stylesheets/components.css index a66432b..e87642b 100644 --- a/src/stylesheets/components.css +++ b/src/stylesheets/components.css @@ -13,3 +13,4 @@ @import url('../../islands/MoreBox.css'); @import url('../../islands/AiChatBox.css'); @import url('../../islands/LoginForm.css'); +@import url('../../islands/IsOnline.css');