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 0000000..0d992ba
Binary files /dev/null and b/public/images/safari-share-icon.png differ