From 987b419018292734123784cb2c4e14308929ad79 Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 24 Sep 2024 23:38:30 -0400 Subject: [PATCH] Clean up instructions --- .prettierrc | 1 + app/install/EnableNotifications.tsx | 61 ++++++++++++++++++ app/install/InstallPWA.tsx | 32 ++++++++++ app/install/InstallPrompts.tsx | 32 ++++++++++ app/install/OpenSafari.tsx | 11 ++++ app/install/Unsupported.tsx | 22 +++++++ app/root.tsx | 4 ++ app/routes/_index.tsx | 96 ++++++---------------------- app/styles/styles.css | 10 +++ app/useInstallState.ts | 46 +++++++++++++ public/images/safari-share-icon.png | Bin 0 -> 2648 bytes 11 files changed, 239 insertions(+), 76 deletions(-) create mode 100644 app/install/EnableNotifications.tsx create mode 100644 app/install/InstallPWA.tsx create mode 100644 app/install/InstallPrompts.tsx create mode 100644 app/install/OpenSafari.tsx create mode 100644 app/install/Unsupported.tsx create mode 100644 app/styles/styles.css create mode 100644 app/useInstallState.ts create mode 100644 public/images/safari-share-icon.png diff --git a/.prettierrc b/.prettierrc index b9a8520..f010f27 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,6 +3,7 @@ "tabWidth": 2, "semi": false, "singleQuote": false, + "maxLineLength": 120, "overrides": [ { "files": [ diff --git a/app/install/EnableNotifications.tsx b/app/install/EnableNotifications.tsx new file mode 100644 index 0000000..ee6112c --- /dev/null +++ b/app/install/EnableNotifications.tsx @@ -0,0 +1,61 @@ +import React from "react" +import { useEffect } from "react" +import { usePush } from "remix-pwa-monorepo/packages/push/client/hook" + +export default function EnableNotifications({ + onSubscribe, +}: { + onSubscribe: () => void +}) { + return ( +
+

Allow Notifications

+
+ Tack Up Now requires your permission to send notifications in order to + function properly +
+ +
+ ) +} + +function EnableButton({ onSubscribe }: { onSubscribe: () => void }) { + const { subscribeToPush, requestPermission, canSendPush } = usePush() + + function subscribe() { + requestPermission() + } + + useEffect(() => { + if (!canSendPush) return + + subscribeToPush( + urlB64ToUint8Array(applicationServerPublicKey) as any, + (subscription) => { + fetch("/api/subscribe", { + method: "POST", + body: JSON.stringify(subscription), + }) + onSubscribe() + } + ) + }, [canSendPush]) + + return +} + +const applicationServerPublicKey = + "BDTbzdtzJxwV0sscdsXla-GKvlcxqQr7edEfkX8-papwvvV1UVc3IMyRacl1BbgTi31nWPji2wKCZkjf1l5iX7Y" + +function urlB64ToUint8Array(base64String: string) { + const padding = "=".repeat((4 - (base64String.length % 4)) % 4) + const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/") + + const rawData = window.atob(base64) + const outputArray = new Uint8Array(rawData.length) + + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i) + } + return outputArray +} diff --git a/app/install/InstallPWA.tsx b/app/install/InstallPWA.tsx new file mode 100644 index 0000000..8b4284a --- /dev/null +++ b/app/install/InstallPWA.tsx @@ -0,0 +1,32 @@ +import React from "react" + +import shareIcon from "../../public/images/safari-share-icon.png" + +export default function InstallPWA() { + return ( +
+

Install Tack Up Now!

+
+ Install Tack Up Now on your device to get notified when it's time to + tack up +
+
+
    + Tap + + and choose + Add to Home Screen +
+
    + On the next screen, tap + Add +
+
    + Then open + Tack Up Now + from your home screen +
+
+
+ ) +} diff --git a/app/install/InstallPrompts.tsx b/app/install/InstallPrompts.tsx new file mode 100644 index 0000000..3760af9 --- /dev/null +++ b/app/install/InstallPrompts.tsx @@ -0,0 +1,32 @@ +import React from "react" +import useInstallState from "../useInstallState" +import EnableNotifications from "./EnableNotifications" +import OpenSafari from "./OpenSafari" +import InstallPWA from "./InstallPWA" +import Unsupported from "./Unsupported" + +interface InstallPromptsProps { + isMobileSafari: boolean + isSupported: boolean + notificationsEnabled: boolean + onInstallComplete: () => void +} + +export default function InstallPrompts({ + isSupported, + isMobileSafari, + onInstallComplete, +}: InstallPromptsProps) { + const { step } = useInstallState({ isSupported, isMobileSafari }) + + const steps = { + "open safari": , + install: , + "enable notifications": ( + + ), + unsupported: , + } + + return steps[step as keyof typeof steps] ?? null +} diff --git a/app/install/OpenSafari.tsx b/app/install/OpenSafari.tsx new file mode 100644 index 0000000..d4b3d9c --- /dev/null +++ b/app/install/OpenSafari.tsx @@ -0,0 +1,11 @@ +import React from "react" + +export default function OpenSafari() { + return ( +
+
This device requires Tack Up Now to be installed using Safari
+
+
Open tackupnow.com in Safari to continue!
+
+ ) +} diff --git a/app/install/Unsupported.tsx b/app/install/Unsupported.tsx new file mode 100644 index 0000000..49f87f1 --- /dev/null +++ b/app/install/Unsupported.tsx @@ -0,0 +1,22 @@ +import React, { useState } from "react" + +export default function Unsupported() { + const [showWhy, setShowWhy] = useState(false) + + return ( +
+

{"Sorry :("}

+
+
Your device doesn't support Tack Up Now!
+ + {showWhy ? ( +
+ iOS 16.3 and under does not support notification delivery through web + apps, so Tack Up Now can't send notifications to your device. +
+ ) : ( + + )} +
+ ) +} diff --git a/app/root.tsx b/app/root.tsx index 58858c3..87b156b 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -7,6 +7,10 @@ import { } from "@remix-run/react" import React from "react" import { ManifestLink } from "@remix-pwa/sw" +import { LinksFunction } from "@remix-run/node" +import styles from "./styles/styles.css?url" + +export const links: LinksFunction = () => [{ rel: "stylesheet", href: styles }] export function Layout({ children }: { children: React.ReactNode }) { return ( diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index ce9112b..cef4d1c 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -8,7 +8,8 @@ import React, { Suspense, useEffect, useState } from "react" import coerceSemver from "semver/functions/coerce" import versionAtLeast from "semver/functions/gte" import UAParser from "ua-parser-js" -import { usePush } from "remix-pwa-monorepo/packages/push/client/hook" +import InstallPrompts from "../install/InstallPrompts" +import useInstallState from "../useInstallState" export const meta: MetaFunction = () => { return [ @@ -21,25 +22,24 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { const userAgent = request.headers.get("user-agent") const parsedUserAgent = new UAParser(userAgent ?? "") const os = parsedUserAgent.getOS() - const isNonSafari = parsedUserAgent.getBrowser().name !== "Mobile Safari" - const browser = parsedUserAgent.getBrowser().name + const isMobileSafari = parsedUserAgent.getBrowser().name !== "Mobile Safari" const isSupported = os.name !== "iOS" || versionAtLeast(coerceSemver(os.version) ?? "0.0.0", "16.4.0") - console.log("Wut", isNonSafari) - - return json({ isSupported, isNonSafari, browser }) + return json({ isSupported, isMobileSafari }) } export default function Index() { - const { isSupported, isNonSafari, browser } = useLoaderData() + const { isSupported, isMobileSafari } = useLoaderData() return (
- - {browser} +
) @@ -47,86 +47,30 @@ export default function Index() { function LandingMessage({ isSupported, - isNonSafari, + isMobileSafari, }: { isSupported: boolean - isNonSafari: boolean + isMobileSafari: boolean }) { - const isClient = typeof window !== "undefined" - const notificationsEnabled = - isClient && - "Notification" in window && - window.Notification.permission === "granted" - - const isRunningPWA = - isClient && - (("standalone" in navigator && (navigator.standalone as boolean)) || - matchMedia("(dislay-mode: standalone)").matches) + const { installed } = useInstallState({ isSupported, isMobileSafari }) const [rendered, setRendered] = useState(false) - const [isInstalled, setIsInstalled] = useState(notificationsEnabled) + const [isInstalled, setIsInstalled] = useState(installed) useEffect(() => { setRendered(true) }, []) - return !isClient || !rendered ? ( + return !rendered ? (
Loading
) : isInstalled ? (
Your Notifications
- ) : isNonSafari ? ( -
Safari
- ) : isRunningPWA && !notificationsEnabled ? ( - setIsInstalled(true)} /> - ) : isSupported ? ( -
Install Tack Up Now!
) : ( -
{"Sorry, your device doesn't support Tack Up Now! :("}
+ setIsInstalled(true)} + /> ) } - -function EnableButton({ onSubscribe }: { onSubscribe: () => void }) { - const { subscribeToPush, requestPermission, canSendPush } = usePush() - - function subscribe() { - requestPermission() - } - - useEffect(() => { - if (!canSendPush) return - - subscribeToPush( - urlB64ToUint8Array(applicationServerPublicKey) as any, - (subscription) => { - fetch("/api/subscribe", { - method: "POST", - body: JSON.stringify(subscription), - }) - onSubscribe() - }, - (error) => { - const errorDiv = document.createElement("div") - errorDiv.innerText = "Error thingy" + JSON.stringify(error) - document.appendChild(errorDiv) - } - ) - }, [canSendPush]) - - return -} - -const applicationServerPublicKey = - "BDTbzdtzJxwV0sscdsXla-GKvlcxqQr7edEfkX8-papwvvV1UVc3IMyRacl1BbgTi31nWPji2wKCZkjf1l5iX7Y" - -function urlB64ToUint8Array(base64String: string) { - const padding = "=".repeat((4 - (base64String.length % 4)) % 4) - const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/") - - const rawData = window.atob(base64) - const outputArray = new Uint8Array(rawData.length) - - for (let i = 0; i < rawData.length; ++i) { - outputArray[i] = rawData.charCodeAt(i) - } - return outputArray -} diff --git a/app/styles/styles.css b/app/styles/styles.css new file mode 100644 index 0000000..bc70d99 --- /dev/null +++ b/app/styles/styles.css @@ -0,0 +1,10 @@ +.bold { + font-weight: bold; +} + +ul { + display: flex; + flex-direction: row; + align-items: center; + white-space: pre-wrap; +} diff --git a/app/useInstallState.ts b/app/useInstallState.ts new file mode 100644 index 0000000..68a61cb --- /dev/null +++ b/app/useInstallState.ts @@ -0,0 +1,46 @@ +import { usePush } from "remix-pwa-monorepo/packages/push/client/hook" + +type IOSInstallStep = + | "loading" + | "install" + | "open safari" + | "enable notifications" + | "unsupported" + +export default function useInstallState({ + isSupported, + isMobileSafari, +}: { + isSupported: boolean + isMobileSafari: boolean +}) { + const isClient = typeof window !== undefined + + if (!isClient) + return { + step: isSupported ? "loading" : ("unsupported" as IOSInstallStep), + installed: false, + } + + const { canSendPush } = usePush() + + const notificationsEnabled = + ("Notification" in window && + window.Notification.permission === "granted") || + canSendPush + + const isRunningPWA = + ("standalone" in navigator && (navigator.standalone as boolean)) || + matchMedia("(dislay-mode: standalone)").matches + + return { + step: !isMobileSafari + ? "open safari" + : !isRunningPWA && isMobileSafari + ? "install" + : !notificationsEnabled + ? "enable notifications" + : (null as IOSInstallStep | null), + installed: notificationsEnabled, + } +} diff --git a/public/images/safari-share-icon.png b/public/images/safari-share-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0d992bac84ce44b466b72be23a09794dbd81078b GIT binary patch literal 2648 zcmV-e3a9mnP)Qe%T5rLkSvW^dVVzvGX& zopWa1@4DUHfp6b;X3m`F%$&=ddEa+wZvGrY6atVM*mM9OW-ut#!Eq1+M5<0io`vn} zQ8p64E&DhSr?%QaBHc{6o&6=%Q+I4M@>tn68s90tE)E<(eMwcBo~SClr>O{MsW%$G z9ZgA@CjoK&YZk`LfuGbypDd6k(6ING&3tr(ZA(rcQ`Y|U@5pMY*V{Uov@#8L{ zdF1&!5P^t5?60W~glq7pa35)axW$Wci1dMn`2FHZ#vKm=-7YXW1$_Geu0H8zhaKe|>8;+fhn(Oj~6$gN?@b z|Cw?3QO~l~2EOre;JP<@%UkYb>hr5!$ZvkwVb4zAcIG+YQ$Jw5xG*|?ZFguoqV&x( z;;vR>*l!z}U0@vgxnBs^0uJs2-g$-a^O`^e-1!m0HCK6x=g$InJzP=`g5Rsu#yY^P z63JE1>8pw%W{h;L+)H@n6k&SWyT)xF1#Wt?-VJKn0Ra;&;O>16yRY)3yDu9JKlTzC zORBjT#Hlq!+LVaos4VlAY$_gvgG-TeEuRogwg?Y=23YM9+D`8dRjmsAsTS~^{e(^z zXgOipL*RqS?}`RY;>-EB1IN6oBuHX%7Dt9Nb~+t0r)V!TL=~Wj&~62uB2%Qw>9mI5 zZXruPBEDE7gD?0Jg_vPGlynd_WoFr#L&QdE@$kWae9(^26EN_GY&n*SXnJ$C)&VkF93F5O4zjx zDUN7g5~avs1-_VnyAUj`v<)xf02P`$UEr=qy7=Yqkq%r<<@{b??+mhrst;l`{)3Nm zAHIy~Y2eE@18?6R?4Rv~iy<0c%-@w$hKR~YyNkRc7|a(Q=;CL;>Aw(Jh`^_^5cVw6 zG#6XWo&~;im~rf1A?}r-$g`~BQzj(6^Y6~k1gP0WlC*SL>&z9v5@Q>5LU!V3$>2`rTe#-dC=d*UxrW5aV94%-V zKwF7ZTT!GSt|oExc&_c(4t)23D~{e0tZ$(b{}bQTv>Pbl%?pT(eX=8Bj8s~ttwc_#IBbB zMo5 z1LK=a*rm>Rva^0xivu-uLBzJrMzjhKmb8Q5_lRLb;ECol|tZDEJSmjRUJ-rf|f`f0%m z0&}fBzc&-%7&FGfx48ZANw1fqCI)d(8qCe6Uh`|au$3`aUhHpMZQ$~)ZghNG2EhXRge6~!-$9GB&X))o|E`@4jzyYts0ckkFDk zXY|+}hr%z$kB;x;z)WW-^`#+*S`;#L+{1!K!EbPrG4P!p*sy0-f^$(CJ1bGFHh5_K zVH1!3_)hmW%-XJEFU+FS)28dR!{ArN84cgn(?PkXHE64mDOYHk_FTJXj)O0;M#C5H z`|N6ZLMr-tZSGbOD+EkkrIfW`SCfP$Gqu7+fr&aKjbj;nU*MqEHK&0sbog#^KMu z#Mpm`vAm>3ZnevVajna02gOtKckcnd_95+NUSc~Rz|Y2hqy^C8(h|coW>&J4On-Z_ zi`(x*u^{VRcJ2V~`50kK8-?QZf`p>J^bre`Ekg&M@M##zK3&e91CBhwc;W=`-`Vw9 zm{!YwZhhq?jGM0|y#FoXyNp6^QEFjB73(JK8`J+cNVW{|eMSV8{)o!*X+4$xYDC{) ze5Vo*ih@MR!jXxA_z*;Uq7PJT1c}i;|CPQhaulVmjmH!%Ui8`EOI+hs7>n!q;ZN zcP!x|CiJ?e&_hy$=y$^3S@+2hMr4JLSepl5J