@indietabletop/appkit 6.0.0 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -5,7 +5,7 @@ import { SafariCheck } from "./SafariCheck.tsx";
|
|
|
5
5
|
|
|
6
6
|
function Content() {
|
|
7
7
|
return (
|
|
8
|
-
|
|
8
|
+
<div>
|
|
9
9
|
<h1>Safari Check passed</h1>
|
|
10
10
|
<button
|
|
11
11
|
onClick={() => {
|
|
@@ -15,14 +15,31 @@ function Content() {
|
|
|
15
15
|
>
|
|
16
16
|
Clear local storage
|
|
17
17
|
</button>
|
|
18
|
-
|
|
18
|
+
</div>
|
|
19
19
|
);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function userAgentResponse(params: { browserName: string }) {
|
|
23
|
+
return http.get("http://mock.api/ua", async () => {
|
|
24
|
+
await sleep(1500);
|
|
25
|
+
return HttpResponse.json({
|
|
26
|
+
browser: { name: params.browserName },
|
|
27
|
+
device: params.browserName.includes("Mobile") ? { type: "mobile" } : {},
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
const meta = preview.meta({
|
|
23
33
|
title: "Components/SafariCheck",
|
|
24
34
|
component: SafariCheck,
|
|
25
35
|
tags: ["autodocs"],
|
|
36
|
+
decorators: [
|
|
37
|
+
(Story) => (
|
|
38
|
+
<div style={{ height: "100svh", display: "grid" }}>
|
|
39
|
+
<Story />
|
|
40
|
+
</div>
|
|
41
|
+
),
|
|
42
|
+
],
|
|
26
43
|
parameters: {
|
|
27
44
|
msw: {
|
|
28
45
|
handlers: {
|
|
@@ -32,9 +49,6 @@ const meta = preview.meta({
|
|
|
32
49
|
},
|
|
33
50
|
});
|
|
34
51
|
|
|
35
|
-
/**
|
|
36
|
-
* The default case in which all steps of the flow succeed.
|
|
37
|
-
*/
|
|
38
52
|
export const SafariDesktop = meta.story({
|
|
39
53
|
args: {
|
|
40
54
|
children: <Content />,
|
|
@@ -48,9 +62,6 @@ export const SafariDesktop = meta.story({
|
|
|
48
62
|
},
|
|
49
63
|
});
|
|
50
64
|
|
|
51
|
-
/**
|
|
52
|
-
* The default case in which all steps of the flow succeed.
|
|
53
|
-
*/
|
|
54
65
|
export const SafariMobile = meta.story({
|
|
55
66
|
args: {
|
|
56
67
|
children: <Content />,
|
|
@@ -64,9 +75,6 @@ export const SafariMobile = meta.story({
|
|
|
64
75
|
},
|
|
65
76
|
});
|
|
66
77
|
|
|
67
|
-
/**
|
|
68
|
-
* The default case in which all steps of the flow succeed.
|
|
69
|
-
*/
|
|
70
78
|
export const OtherBrowser = meta.story({
|
|
71
79
|
args: {
|
|
72
80
|
children: <Content />,
|
|
@@ -80,12 +88,12 @@ export const OtherBrowser = meta.story({
|
|
|
80
88
|
},
|
|
81
89
|
});
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
}
|
|
91
|
+
/**
|
|
92
|
+
* The check behaviour has been disabled.
|
|
93
|
+
*/
|
|
94
|
+
export const Disabled = meta.story({
|
|
95
|
+
args: {
|
|
96
|
+
children: <Content />,
|
|
97
|
+
performCheck: false,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
@@ -2,6 +2,7 @@ import { Button, DialogDisclosure, DialogDismiss } from "@ariakit/react";
|
|
|
2
2
|
import { useCallback, useMemo, useState, type ReactNode } from "react";
|
|
3
3
|
import { number } from "superstruct";
|
|
4
4
|
import useImmutableSWR from "swr/immutable";
|
|
5
|
+
import { Link } from "wouter";
|
|
5
6
|
import { useAppConfig, useClient } from "../AppConfig/AppConfig.tsx";
|
|
6
7
|
import { Failure, Pending, Success } from "../async-op.ts";
|
|
7
8
|
import { interactiveText } from "../common.css.ts";
|
|
@@ -16,6 +17,7 @@ import { button } from "../Letterhead/style.css.ts";
|
|
|
16
17
|
import { LoadingIndicator } from "../LoadingIndicator.tsx";
|
|
17
18
|
import { ModalDialog } from "../ModalDialog/index.tsx";
|
|
18
19
|
import { swrResponseToResult } from "../result/swr.ts";
|
|
20
|
+
import { useCurrentUser } from "../store/index.tsx";
|
|
19
21
|
import type { FailurePayload } from "../types.ts";
|
|
20
22
|
import { useIsInstalled } from "../use-is-installed.ts";
|
|
21
23
|
import addToDockUrl from "./addToDock.svg";
|
|
@@ -133,9 +135,9 @@ function SafariPrompt(props: { onDismiss: () => void; isMobile?: boolean }) {
|
|
|
133
135
|
<DialogDisclosure className={button()}>Install App</DialogDisclosure>
|
|
134
136
|
</DialogTrigger>
|
|
135
137
|
|
|
136
|
-
<
|
|
138
|
+
<Link className={button()} href={hrefs.join()}>
|
|
137
139
|
Create Account
|
|
138
|
-
</
|
|
140
|
+
</Link>
|
|
139
141
|
</div>
|
|
140
142
|
|
|
141
143
|
<div className={css.safariDismissArea}>
|
|
@@ -145,6 +147,7 @@ function SafariPrompt(props: { onDismiss: () => void; isMobile?: boolean }) {
|
|
|
145
147
|
</Button>
|
|
146
148
|
</LetterheadParagraph>
|
|
147
149
|
</div>
|
|
150
|
+
|
|
148
151
|
<LetterheadFooter />
|
|
149
152
|
</div>
|
|
150
153
|
);
|
|
@@ -184,23 +187,19 @@ type SafariCheckResult =
|
|
|
184
187
|
| Failure<FailurePayload>
|
|
185
188
|
| Pending;
|
|
186
189
|
|
|
187
|
-
function useSafariCheck() {
|
|
190
|
+
function useSafariCheck({ performCheck }: { performCheck: boolean }) {
|
|
191
|
+
const isAuthenticated = !!useCurrentUser();
|
|
188
192
|
const isInstalled = useIsInstalled();
|
|
189
193
|
const { dismissedAt, setDismissed } = useInstallPromptState();
|
|
190
194
|
const installedOrDismissed = isInstalled || !!dismissedAt;
|
|
191
|
-
const userAgentResult = useUserAgent({
|
|
192
|
-
|
|
193
|
-
console.log({
|
|
194
|
-
isInstalled,
|
|
195
|
-
dismissedAt,
|
|
196
|
-
installedOrDismissed,
|
|
197
|
-
userAgentResult,
|
|
195
|
+
const userAgentResult = useUserAgent({
|
|
196
|
+
performFetch: performCheck && !isAuthenticated && !installedOrDismissed,
|
|
198
197
|
});
|
|
199
198
|
|
|
200
199
|
const result = useMemo((): SafariCheckResult => {
|
|
201
|
-
return installedOrDismissed
|
|
202
|
-
? // If the safari prompt was previously dismissed,
|
|
203
|
-
// installed, we
|
|
200
|
+
return isAuthenticated || installedOrDismissed || !performCheck
|
|
201
|
+
? // If the safari prompt was previously dismissed, the app is already
|
|
202
|
+
// installed, or check was disabled explicitly, we skip the prompt.
|
|
204
203
|
new Success({ showPrompt: false })
|
|
205
204
|
: // Otherwise, we want to check whether the name includes Safari (could be
|
|
206
205
|
// Safari or Safari Mobile), and we also report whether this is a mobile
|
|
@@ -211,22 +210,48 @@ function useSafariCheck() {
|
|
|
211
210
|
isMobile: value.device?.type === "mobile",
|
|
212
211
|
};
|
|
213
212
|
});
|
|
214
|
-
}, [installedOrDismissed, userAgentResult]);
|
|
213
|
+
}, [installedOrDismissed, userAgentResult, performCheck, isAuthenticated]);
|
|
215
214
|
|
|
216
215
|
return { result, setDismissed };
|
|
217
216
|
}
|
|
218
217
|
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Checks whether the browser which is running the app is Safari/Safari Mobile
|
|
220
|
+
* and warns the user that their data might be deleted due to inactivity.
|
|
221
|
+
*
|
|
222
|
+
* The warning will be shown if:
|
|
223
|
+
*
|
|
224
|
+
* - The check has not been explicitly disabled via `performCheck: false`
|
|
225
|
+
* - The warning has not been previously dismissed (as reported by localStorage)
|
|
226
|
+
* - The app is not installed (Safari behaves differently in that case)
|
|
227
|
+
* - The user is not already logged in (as data will be safely backed up that way)
|
|
228
|
+
*/
|
|
229
|
+
export function SafariCheck(props: {
|
|
230
|
+
children: ReactNode;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Optionally opt out of the check.
|
|
234
|
+
*
|
|
235
|
+
* This can be useful in cases where, e.g. the user already has some content
|
|
236
|
+
* in the app and we want to avoid checking the user agent, but it is
|
|
237
|
+
* impractical to entirely avoid rendering this component.
|
|
238
|
+
*/
|
|
239
|
+
performCheck?: boolean;
|
|
240
|
+
}) {
|
|
241
|
+
const { result, setDismissed } = useSafariCheck({
|
|
242
|
+
performCheck: props.performCheck ?? true,
|
|
243
|
+
});
|
|
221
244
|
|
|
222
245
|
if (result.isPending) {
|
|
223
246
|
return (
|
|
224
|
-
<div className={css.
|
|
225
|
-
<
|
|
247
|
+
<div className={css.container}>
|
|
248
|
+
<div className={css.compatCheckLoaderContainer}>
|
|
249
|
+
<LoadingIndicator className={css.indicator} />
|
|
226
250
|
|
|
227
|
-
|
|
251
|
+
{/* Intentionally not using Letterhead Paragraph to pick up the default
|
|
228
252
|
font size and style. */}
|
|
229
|
-
|
|
253
|
+
<p className={css.compatCheckLoaderText}>Checking compatibility...</p>
|
|
254
|
+
</div>
|
|
230
255
|
</div>
|
|
231
256
|
);
|
|
232
257
|
}
|
|
@@ -236,7 +261,11 @@ export function SafariCheck(props: { children: ReactNode }) {
|
|
|
236
261
|
// keep rolling as if the showPrompt was false.
|
|
237
262
|
const value = result.valueOrNull();
|
|
238
263
|
if (value?.showPrompt) {
|
|
239
|
-
return
|
|
264
|
+
return (
|
|
265
|
+
<div className={css.container}>
|
|
266
|
+
<SafariPrompt onDismiss={setDismissed} isMobile={value.isMobile} />
|
|
267
|
+
</div>
|
|
268
|
+
);
|
|
240
269
|
}
|
|
241
270
|
|
|
242
271
|
// We're all good, render children.
|
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { style } from "@vanilla-extract/css";
|
|
2
2
|
import { fadeIn } from "../animations.css.ts";
|
|
3
|
-
import { MinWidth } from "../media.ts";
|
|
4
3
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
export const safariPromptContainer = style({
|
|
4
|
+
export const container = style({
|
|
8
5
|
display: "flex",
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
padding: "1rem",
|
|
6
|
+
alignItems: "center",
|
|
7
|
+
justifyItems: "center",
|
|
8
|
+
flexDirection: "column",
|
|
13
9
|
blockSize: "100%",
|
|
14
|
-
|
|
15
|
-
"@media": {
|
|
16
|
-
[MinWidth.MEDIUM]: {
|
|
17
|
-
alignItems: "center",
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
10
|
});
|
|
21
11
|
|
|
22
12
|
export const safariPrompt = style({
|
|
@@ -24,6 +14,7 @@ export const safariPrompt = style({
|
|
|
24
14
|
borderRadius: "1rem",
|
|
25
15
|
maxWidth: "38rem",
|
|
26
16
|
padding: "clamp(1rem, 5vw, 3rem)",
|
|
17
|
+
margin: "auto",
|
|
27
18
|
});
|
|
28
19
|
|
|
29
20
|
export const safariLogo = style({
|
|
@@ -51,7 +42,9 @@ export const safariDismissArea = style({
|
|
|
51
42
|
|
|
52
43
|
export const compatCheckLoaderContainer = style({
|
|
53
44
|
maxInlineSize: "16rem",
|
|
45
|
+
margin: "auto",
|
|
54
46
|
});
|
|
47
|
+
|
|
55
48
|
export const indicator = style({
|
|
56
49
|
marginInline: "auto",
|
|
57
50
|
});
|