@indietabletop/appkit 7.0.0-0 → 7.0.0-rc.0
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.
- package/lib/AppConfig/AppConfig.tsx +80 -0
- package/lib/AppConfig/formatters.tsx +43 -0
- package/lib/AuthCard/AuthCard.stories.ts +34 -0
- package/lib/AuthCard/AuthCard.tsx +64 -0
- package/lib/AuthCard/style.css.ts +49 -0
- package/lib/CacheProvider.tsx +20 -0
- package/lib/DialogTrigger/index.tsx +36 -0
- package/lib/DocumentTitle/DocumentTitle.tsx +10 -0
- package/lib/EnumMapper.ts +50 -0
- package/lib/ExternalLink.tsx +10 -0
- package/lib/FullscreenDismissBlocker.tsx +23 -0
- package/{dist/HistoryState.d.ts → lib/HistoryState.ts} +5 -2
- package/lib/IndieTabletopClubLogo.tsx +44 -0
- package/lib/IndieTabletopClubSymbol.tsx +37 -0
- package/lib/InfoPage/index.tsx +46 -0
- package/lib/InfoPage/pages.tsx +36 -0
- package/lib/InfoPage/style.css.ts +36 -0
- package/lib/Letterhead/index.tsx +85 -0
- package/lib/Letterhead/stories.tsx +41 -0
- package/lib/Letterhead/style.css.ts +152 -0
- package/lib/LetterheadForm/LetterheadReadonlyTextField.stories.tsx +17 -0
- package/lib/LetterheadForm/LetterheadSubmitError.stories.tsx +19 -0
- package/lib/LetterheadForm/LetterheadTextField.stories.tsx +19 -0
- package/lib/LetterheadForm/index.tsx +137 -0
- package/lib/LetterheadForm/style.css.ts +89 -0
- package/lib/LoadingIndicator.tsx +40 -0
- package/lib/MiddotSeparated/MiddotSeparated.stories.ts +26 -0
- package/lib/MiddotSeparated/MiddotSeparated.tsx +26 -0
- package/lib/MiddotSeparated/style.css.ts +10 -0
- package/lib/ModalDialog/index.tsx +28 -0
- package/lib/ModalDialog/style.css.ts +88 -0
- package/lib/ModernIDB/Cursor.ts +91 -0
- package/lib/ModernIDB/ModernIDB.ts +337 -0
- package/lib/ModernIDB/ModernIDBError.ts +9 -0
- package/lib/ModernIDB/ObjectStore.ts +195 -0
- package/lib/ModernIDB/ObjectStoreIndex.ts +102 -0
- package/lib/ModernIDB/README.md +9 -0
- package/lib/ModernIDB/Transaction.ts +40 -0
- package/lib/ModernIDB/VersionChangeManager.ts +57 -0
- package/lib/ModernIDB/bindings/factory.tsx +165 -0
- package/lib/ModernIDB/bindings/index.ts +2 -0
- package/{dist/ModernIDB/bindings/types.d.ts → lib/ModernIDB/bindings/types.ts} +32 -13
- package/lib/ModernIDB/bindings/utils.tsx +32 -0
- package/lib/ModernIDB/index.ts +10 -0
- package/lib/ModernIDB/types.ts +120 -0
- package/lib/ModernIDB/utils.ts +51 -0
- package/lib/QRCode/QRCode.stories.tsx +41 -0
- package/lib/QRCode/QRCode.tsx +54 -0
- package/lib/QRCode/style.css.ts +23 -0
- package/lib/ReleaseInfo/index.tsx +29 -0
- package/lib/RulesetResolver.ts +214 -0
- package/lib/SafariCheck/SafariCheck.stories.tsx +99 -0
- package/lib/SafariCheck/SafariCheck.tsx +273 -0
- package/lib/SafariCheck/addToDock.svg +13 -0
- package/lib/SafariCheck/addToHomeScreen.svg +12 -0
- package/lib/SafariCheck/safari.svg +32 -0
- package/lib/SafariCheck/shareIcon.svg +11 -0
- package/lib/SafariCheck/style.css.ts +106 -0
- package/lib/ServiceWorkerHandler.tsx +53 -0
- package/lib/ShareButton/ShareButton.stories.tsx +58 -0
- package/lib/ShareButton/ShareButton.tsx +153 -0
- package/lib/ShareButton/test.css.ts +3 -0
- package/lib/SubscribeCard/LetterheadInfoCard.tsx +23 -0
- package/lib/SubscribeCard/SubscribeByEmailCard.stories.tsx +69 -0
- package/lib/SubscribeCard/SubscribeByEmailCard.tsx +183 -0
- package/lib/SubscribeCard/SubscribeByPledgeCard.stories.tsx +133 -0
- package/lib/SubscribeCard/SubscribeByPledgeCard.tsx +127 -0
- package/lib/SubscribeCard/style.css.ts +14 -0
- package/lib/Sync/SyncIcon.stories.tsx +67 -0
- package/lib/Sync/SyncIcon.tsx +102 -0
- package/lib/Sync/SyncLog.tsx +222 -0
- package/lib/Sync/SyncLogList.stories.tsx +219 -0
- package/lib/Sync/style.css.ts +126 -0
- package/lib/account/AccountIcon.tsx +15 -0
- package/lib/account/AccountIssueView.tsx +44 -0
- package/lib/account/AlreadyLoggedInView.tsx +47 -0
- package/lib/account/CurrentUserFetcher.stories.tsx +292 -0
- package/lib/account/CurrentUserFetcher.tsx +118 -0
- package/lib/account/FailureFallbackView.tsx +36 -0
- package/lib/account/JoinCard.stories.tsx +257 -0
- package/lib/account/JoinCard.tsx +301 -0
- package/lib/account/LoadingView.tsx +14 -0
- package/lib/account/LoginCard.stories.tsx +288 -0
- package/lib/account/LoginCard.tsx +100 -0
- package/lib/account/LoginView.tsx +151 -0
- package/lib/account/NoConnectionView.tsx +34 -0
- package/lib/account/PasswordResetCard.stories.tsx +242 -0
- package/lib/account/PasswordResetCard.tsx +296 -0
- package/lib/account/UserMismatchView.tsx +62 -0
- package/lib/account/VerifyPage.tsx +195 -0
- package/lib/account/iconMask.svg +3 -0
- package/lib/account/style.css.ts +81 -0
- package/{dist/account/types.d.ts → lib/account/types.ts} +6 -3
- package/lib/account/useFetchCurrentUser.tsx +63 -0
- package/lib/account/useRedirectPath.ts +21 -0
- package/lib/animations.css.ts +17 -0
- package/lib/append-copy-to-text.ts +35 -0
- package/lib/async-op.ts +286 -0
- package/lib/atomic.css.ts +11 -0
- package/{dist/caught-value.d.ts → lib/caught-value.ts} +11 -1
- package/{dist/class-names.d.ts → lib/class-names.ts} +17 -6
- package/lib/client.ts +662 -0
- package/lib/common.css.ts +48 -0
- package/lib/copyrightRange.ts +10 -0
- package/lib/createSafeStorage.ts +91 -0
- package/lib/createStrictContext.ts +15 -0
- package/lib/failureMessages.ts +108 -0
- package/lib/form/FormSubmitButton.tsx +58 -0
- package/lib/form/SubmitErrorAlert.tsx +21 -0
- package/lib/form/style.css.ts +9 -0
- package/lib/globals.css.ts +62 -0
- package/lib/groupBy.ts +25 -0
- package/lib/hrefs.ts +48 -0
- package/lib/idToDate.ts +8 -0
- package/lib/ids.ts +6 -0
- package/lib/index.ts +75 -0
- package/lib/internal.css.ts +10 -0
- package/lib/mailto.ts +40 -0
- package/lib/media.ts +50 -0
- package/lib/random.ts +19 -0
- package/lib/result/swr.ts +18 -0
- package/{dist/sleep.d.ts → lib/sleep.ts} +3 -1
- package/lib/store/index.tsx +294 -0
- package/lib/store/store.ts +482 -0
- package/lib/store/types.ts +45 -0
- package/lib/store/utils.ts +54 -0
- package/lib/storybook/decorators.tsx +10 -0
- package/lib/structs.ts +3 -0
- package/{dist/typeguards.d.ts → lib/typeguards.ts} +3 -1
- package/{dist/types.d.ts → lib/types.ts} +23 -12
- package/lib/unique.ts +24 -0
- package/lib/use-async-op.ts +16 -0
- package/lib/use-document-background-color.ts +16 -0
- package/lib/use-form.ts +78 -0
- package/lib/use-is-installed.ts +17 -0
- package/lib/use-media-query.ts +21 -0
- package/lib/use-reverting-state.ts +32 -0
- package/lib/use-scroll-restoration.ts +99 -0
- package/lib/useEnsureValue.ts +31 -0
- package/lib/useInvokeClient.ts +54 -0
- package/lib/useIsVisible.ts +27 -0
- package/lib/utm.ts +92 -0
- package/lib/validations.ts +25 -0
- package/lib/vars.css.ts +13 -0
- package/package.json +23 -29
- package/dist/AppConfig/AppConfig.d.ts +0 -29
- package/dist/AuthCard/AuthCard.d.ts +0 -10
- package/dist/AuthCard/AuthCard.stories.d.ts +0 -34
- package/dist/AuthCard/style.css.d.ts +0 -23
- package/dist/DialogTrigger/index.d.ts +0 -13
- package/dist/DocumentTitle/DocumentTitle.d.ts +0 -3
- package/dist/EnumMapper.d.ts +0 -25
- package/dist/ExternalLink.d.ts +0 -3
- package/dist/FullscreenDismissBlocker.d.ts +0 -5
- package/dist/IndieTabletopClubLogo.d.ts +0 -7
- package/dist/IndieTabletopClubSymbol.d.ts +0 -7
- package/dist/InfoPage/index.d.ts +0 -8
- package/dist/InfoPage/pages.d.ts +0 -2
- package/dist/InfoPage/style.css.d.ts +0 -5
- package/dist/Letterhead/index.d.ts +0 -19
- package/dist/Letterhead/stories.d.ts +0 -13
- package/dist/Letterhead/style.css.d.ts +0 -46
- package/dist/LetterheadForm/LetterheadReadonlyTextField.stories.d.ts +0 -17
- package/dist/LetterheadForm/LetterheadSubmitError.stories.d.ts +0 -11
- package/dist/LetterheadForm/LetterheadTextField.stories.d.ts +0 -336
- package/dist/LetterheadForm/index.d.ts +0 -44
- package/dist/LetterheadForm/style.css.d.ts +0 -8
- package/dist/LoadingIndicator.d.ts +0 -3
- package/dist/MiddotSeparated/MiddotSeparated.d.ts +0 -8
- package/dist/MiddotSeparated/MiddotSeparated.stories.d.ts +0 -586
- package/dist/MiddotSeparated/style.css.d.ts +0 -1
- package/dist/ModalDialog/index.d.ts +0 -12
- package/dist/ModalDialog/style.css.d.ts +0 -58
- package/dist/ModernIDB/Cursor.d.ts +0 -56
- package/dist/ModernIDB/ModernIDB.d.ts +0 -66
- package/dist/ModernIDB/ModernIDBError.d.ts +0 -3
- package/dist/ModernIDB/ObjectStore.d.ts +0 -112
- package/dist/ModernIDB/ObjectStoreIndex.d.ts +0 -53
- package/dist/ModernIDB/Transaction.d.ts +0 -16
- package/dist/ModernIDB/VersionChangeManager.d.ts +0 -30
- package/dist/ModernIDB/bindings/factory.d.ts +0 -12
- package/dist/ModernIDB/bindings/index.d.ts +0 -2
- package/dist/ModernIDB/bindings/utils.d.ts +0 -2
- package/dist/ModernIDB/index.d.ts +0 -10
- package/dist/ModernIDB/types.d.ts +0 -88
- package/dist/ModernIDB/utils.d.ts +0 -4
- package/dist/QRCode/QRCode.d.ts +0 -7
- package/dist/QRCode/QRCode.stories.d.ts +0 -33
- package/dist/QRCode/style.css.d.ts +0 -4
- package/dist/ReleaseInfo/index.d.ts +0 -5
- package/dist/RulesetResolver.d.ts +0 -87
- package/dist/SafariCheck/SafariCheck.d.ts +0 -23
- package/dist/SafariCheck/SafariCheck.stories.d.ts +0 -73
- package/dist/SafariCheck/style.css.d.ts +0 -17
- package/dist/ServiceWorkerHandler.d.ts +0 -11
- package/dist/ShareButton/ShareButton.d.ts +0 -57
- package/dist/ShareButton/ShareButton.stories.d.ts +0 -1577
- package/dist/ShareButton/test.css.d.ts +0 -1
- package/dist/SubscribeCard/LetterheadInfoCard.d.ts +0 -2
- package/dist/SubscribeCard/SubscribeByEmailCard.d.ts +0 -24
- package/dist/SubscribeCard/SubscribeByEmailCard.stories.d.ts +0 -10
- package/dist/SubscribeCard/SubscribeByPledgeCard.d.ts +0 -36
- package/dist/SubscribeCard/SubscribeByPledgeCard.stories.d.ts +0 -65
- package/dist/SubscribeCard/style.css.d.ts +0 -4
- package/dist/account/AccountIssueView.d.ts +0 -3
- package/dist/account/AlreadyLoggedInView.d.ts +0 -5
- package/dist/account/CurrentUserFetcher.d.ts +0 -20
- package/dist/account/CurrentUserFetcher.stories.d.ts +0 -136
- package/dist/account/FailureFallbackView.d.ts +0 -1
- package/dist/account/JoinCard.d.ts +0 -14
- package/dist/account/JoinCard.stories.d.ts +0 -143
- package/dist/account/LoadingView.d.ts +0 -1
- package/dist/account/LoginCard.d.ts +0 -39
- package/dist/account/LoginCard.stories.d.ts +0 -217
- package/dist/account/LoginView.d.ts +0 -10
- package/dist/account/NoConnectionView.d.ts +0 -4
- package/dist/account/PasswordResetCard.d.ts +0 -15
- package/dist/account/PasswordResetCard.stories.d.ts +0 -128
- package/dist/account/UserMismatchView.d.ts +0 -6
- package/dist/account/VerifyPage.d.ts +0 -13
- package/dist/account/style.css.d.ts +0 -10
- package/dist/account/useFetchCurrentUser.d.ts +0 -28
- package/dist/account/useRedirectPath.d.ts +0 -6
- package/dist/animations.css.d.ts +0 -3
- package/dist/append-copy-to-text.d.ts +0 -10
- package/dist/append-copy-to-text.test.d.ts +0 -1
- package/dist/appkit.css +0 -1
- package/dist/appkit.js +0 -10692
- package/dist/async-op.d.ts +0 -101
- package/dist/atomic.css.d.ts +0 -6
- package/dist/client.d.ts +0 -424
- package/dist/common.css.d.ts +0 -5
- package/dist/copyrightRange.d.ts +0 -1
- package/dist/copyrightRange.test.d.ts +0 -1
- package/dist/createSafeStorage.d.ts +0 -34
- package/dist/failureMessages.d.ts +0 -20
- package/dist/failureMessages.test.d.ts +0 -1
- package/dist/form/FormSubmitButton.d.ts +0 -17
- package/dist/form/SubmitErrorAlert.d.ts +0 -5
- package/dist/form/style.css.d.ts +0 -3
- package/dist/globals.css.d.ts +0 -0
- package/dist/groupBy.d.ts +0 -1
- package/dist/groupBy.test.d.ts +0 -1
- package/dist/hrefs.d.ts +0 -32
- package/dist/hrefs.test.d.ts +0 -1
- package/dist/idToDate.d.ts +0 -5
- package/dist/idToDate.test.d.ts +0 -1
- package/dist/ids.d.ts +0 -1
- package/dist/ids.test.d.ts +0 -1
- package/dist/index.d.ts +0 -64
- package/dist/internal.css.d.ts +0 -2
- package/dist/mailto.d.ts +0 -8
- package/dist/mailto.test.d.ts +0 -1
- package/dist/media.d.ts +0 -39
- package/dist/random.d.ts +0 -3
- package/dist/result/swr.d.ts +0 -4
- package/dist/store/index.d.ts +0 -237
- package/dist/store/store.d.ts +0 -144
- package/dist/store/types.d.ts +0 -49
- package/dist/store/utils.d.ts +0 -10
- package/dist/storybook/decorators.d.ts +0 -3
- package/dist/structs.d.ts +0 -1
- package/dist/typeguards.test.d.ts +0 -1
- package/dist/unique.d.ts +0 -10
- package/dist/unique.test.d.ts +0 -1
- package/dist/use-async-op.d.ts +0 -6
- package/dist/use-document-background-color.d.ts +0 -4
- package/dist/use-form.d.ts +0 -29
- package/dist/use-is-installed.d.ts +0 -8
- package/dist/use-media-query.d.ts +0 -1
- package/dist/use-reverting-state.d.ts +0 -5
- package/dist/use-scroll-restoration.d.ts +0 -25
- package/dist/useEnsureValue.d.ts +0 -6
- package/dist/useInvokeClient.d.ts +0 -25
- package/dist/useIsVisible.d.ts +0 -4
- package/dist/utm.d.ts +0 -58
- package/dist/utm.test.d.ts +0 -1
- package/dist/validations.d.ts +0 -3
- package/dist/vars.css.d.ts +0 -10
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createThemeContract,
|
|
3
|
+
fallbackVar,
|
|
4
|
+
keyframes,
|
|
5
|
+
style,
|
|
6
|
+
} from "@vanilla-extract/css";
|
|
7
|
+
import { recipe } from "@vanilla-extract/recipes";
|
|
8
|
+
|
|
9
|
+
const bounce = keyframes({
|
|
10
|
+
"0%": { translate: "0 0" },
|
|
11
|
+
"25%": { translate: "0 -10%" },
|
|
12
|
+
"50%": { translate: "0 0" },
|
|
13
|
+
"100%": { translate: "0 0" },
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const rotation = keyframes({
|
|
17
|
+
from: { transform: `rotate(0deg)` },
|
|
18
|
+
to: { transform: `rotate(360deg)` },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const syncTheme = createThemeContract({
|
|
22
|
+
idle: null,
|
|
23
|
+
inactive: null,
|
|
24
|
+
syncing: null,
|
|
25
|
+
error: null,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const backplate = style({
|
|
29
|
+
cx: 10,
|
|
30
|
+
cy: 10,
|
|
31
|
+
r: 10,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const icon = {
|
|
35
|
+
container: recipe({
|
|
36
|
+
base: {},
|
|
37
|
+
|
|
38
|
+
variants: {
|
|
39
|
+
rotate: {
|
|
40
|
+
true: {
|
|
41
|
+
animation: `${rotation} linear 2s both infinite`,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
|
|
47
|
+
group: style({
|
|
48
|
+
stroke: "none",
|
|
49
|
+
fill: "none",
|
|
50
|
+
fillRule: "evenodd",
|
|
51
|
+
}),
|
|
52
|
+
|
|
53
|
+
shape: style({
|
|
54
|
+
fill: "white",
|
|
55
|
+
}),
|
|
56
|
+
|
|
57
|
+
idle: style([
|
|
58
|
+
backplate,
|
|
59
|
+
{
|
|
60
|
+
fill: fallbackVar(syncTheme.idle, "green"),
|
|
61
|
+
},
|
|
62
|
+
]),
|
|
63
|
+
|
|
64
|
+
inactive: style([
|
|
65
|
+
backplate,
|
|
66
|
+
{
|
|
67
|
+
fill: fallbackVar(syncTheme.inactive, "blue"),
|
|
68
|
+
},
|
|
69
|
+
]),
|
|
70
|
+
|
|
71
|
+
error: style([
|
|
72
|
+
backplate,
|
|
73
|
+
{
|
|
74
|
+
fill: fallbackVar(syncTheme.error, "red"),
|
|
75
|
+
},
|
|
76
|
+
]),
|
|
77
|
+
|
|
78
|
+
syncing: style([
|
|
79
|
+
backplate,
|
|
80
|
+
{
|
|
81
|
+
fill: fallbackVar(syncTheme.syncing, "purple"),
|
|
82
|
+
},
|
|
83
|
+
]),
|
|
84
|
+
|
|
85
|
+
bounce: style({
|
|
86
|
+
vars: {
|
|
87
|
+
"--duration": "1.5s",
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
animation: `${bounce} var(--duration) both infinite`,
|
|
91
|
+
r: 1,
|
|
92
|
+
fill: "white",
|
|
93
|
+
|
|
94
|
+
selectors: {
|
|
95
|
+
"&:nth-child(2)": {
|
|
96
|
+
animationDelay: "calc(var(--duration) * -0.1)",
|
|
97
|
+
},
|
|
98
|
+
"&:nth-child(3)": {
|
|
99
|
+
animationDelay: "calc(var(--duration) * -0.2)",
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const log = {
|
|
106
|
+
item: style({
|
|
107
|
+
display: "grid",
|
|
108
|
+
gridTemplateColumns: "auto minmax(0, 1fr)",
|
|
109
|
+
alignItems: "center",
|
|
110
|
+
gap: "1rem",
|
|
111
|
+
padding: "1rem 0",
|
|
112
|
+
textAlign: "start",
|
|
113
|
+
borderBlockStart: "1px solid hsl(0 0% 90%)",
|
|
114
|
+
inlineSize: "100%",
|
|
115
|
+
}),
|
|
116
|
+
|
|
117
|
+
disclosureContent: style({
|
|
118
|
+
display: "grid",
|
|
119
|
+
gap: "1rem",
|
|
120
|
+
borderInlineStart: "1px solid hsl(0 0% 90%)",
|
|
121
|
+
paddingInlineStart: "1rem",
|
|
122
|
+
marginBlockEnd: "1.5rem",
|
|
123
|
+
}),
|
|
124
|
+
|
|
125
|
+
itemTimestamp: style({ opacity: 0.5 }),
|
|
126
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useSyncState } from "../store/index.tsx";
|
|
2
|
+
import { SyncIcon } from "../Sync/SyncIcon.tsx";
|
|
3
|
+
import { accountIcon } from "./style.css.ts";
|
|
4
|
+
|
|
5
|
+
export function AccountIcon(props: { imgSrc: string }) {
|
|
6
|
+
const { imgSrc } = props;
|
|
7
|
+
const syncState = useSyncState();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div className={accountIcon.container}>
|
|
11
|
+
<SyncIcon status={syncState} className={accountIcon.syncIcon} />
|
|
12
|
+
<img src={imgSrc} alt="" className={accountIcon.image} />
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Button } from "@ariakit/react";
|
|
2
|
+
import { cx } from "../class-names.ts";
|
|
3
|
+
import { interactiveText } from "../common.css.ts";
|
|
4
|
+
import {
|
|
5
|
+
Letterhead,
|
|
6
|
+
LetterheadHeading,
|
|
7
|
+
LetterheadParagraph,
|
|
8
|
+
} from "../Letterhead/index.tsx";
|
|
9
|
+
import { useAppActions } from "../store/index.tsx";
|
|
10
|
+
|
|
11
|
+
export function AccountIssueView(props: { reload: () => void }) {
|
|
12
|
+
const { reload } = props;
|
|
13
|
+
const { logout } = useAppActions();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Letterhead>
|
|
17
|
+
<LetterheadHeading>Account issue</LetterheadHeading>
|
|
18
|
+
|
|
19
|
+
<LetterheadParagraph>
|
|
20
|
+
You appear to be logged into an account that no longer exists, or is not
|
|
21
|
+
working as expected. Sorry about that!
|
|
22
|
+
</LetterheadParagraph>
|
|
23
|
+
|
|
24
|
+
<LetterheadParagraph>
|
|
25
|
+
{"You can try "}
|
|
26
|
+
<Button
|
|
27
|
+
{...cx(interactiveText)}
|
|
28
|
+
onClick={async () => {
|
|
29
|
+
await logout();
|
|
30
|
+
reload();
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
logging out
|
|
34
|
+
</Button>
|
|
35
|
+
{" and in again. "}
|
|
36
|
+
{"If the issue persists, please get in touch at "}
|
|
37
|
+
<a {...cx(interactiveText)} href="mailto:support@indietabletop.club">
|
|
38
|
+
support@indietabletop.club
|
|
39
|
+
</a>
|
|
40
|
+
{"."}
|
|
41
|
+
</LetterheadParagraph>
|
|
42
|
+
</Letterhead>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Button } from "@ariakit/react";
|
|
2
|
+
import { Link } from "wouter";
|
|
3
|
+
import { useAppConfig } from "../AppConfig/AppConfig.tsx";
|
|
4
|
+
import { interactiveText } from "../common.css.ts";
|
|
5
|
+
import {
|
|
6
|
+
Letterhead,
|
|
7
|
+
LetterheadHeading,
|
|
8
|
+
LetterheadParagraph,
|
|
9
|
+
} from "../Letterhead/index.tsx";
|
|
10
|
+
import { useAppActions } from "../store/index.tsx";
|
|
11
|
+
import type { CurrentUser } from "../types.ts";
|
|
12
|
+
|
|
13
|
+
export function AlreadyLoggedInView(props: {
|
|
14
|
+
currentUser: CurrentUser;
|
|
15
|
+
reload: () => void;
|
|
16
|
+
}) {
|
|
17
|
+
const { currentUser, reload } = props;
|
|
18
|
+
const { hrefs } = useAppConfig();
|
|
19
|
+
const { logout } = useAppActions();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Letterhead>
|
|
23
|
+
<LetterheadHeading>Logged in</LetterheadHeading>
|
|
24
|
+
<LetterheadParagraph>
|
|
25
|
+
You are already logged into Indie Tabletop Club as{" "}
|
|
26
|
+
<em>{currentUser.email}</em>.
|
|
27
|
+
</LetterheadParagraph>
|
|
28
|
+
|
|
29
|
+
<LetterheadParagraph>
|
|
30
|
+
<Link className={interactiveText} href={hrefs.dashboard()}>
|
|
31
|
+
Continue
|
|
32
|
+
</Link>
|
|
33
|
+
{` as the current user, or `}
|
|
34
|
+
<Button
|
|
35
|
+
className={interactiveText}
|
|
36
|
+
onClick={async () => {
|
|
37
|
+
await logout();
|
|
38
|
+
reload();
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
log out
|
|
42
|
+
</Button>
|
|
43
|
+
.
|
|
44
|
+
</LetterheadParagraph>
|
|
45
|
+
</Letterhead>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { http, HttpResponse } from "msw";
|
|
2
|
+
import preview from "../../.storybook/preview.tsx";
|
|
3
|
+
import { sleep } from "../sleep.ts";
|
|
4
|
+
import type { CurrentUser, SessionInfo } from "../types.ts";
|
|
5
|
+
import { CurrentUserFetcher } from "./CurrentUserFetcher.tsx";
|
|
6
|
+
|
|
7
|
+
function createMocks(options?: { responseSpeed?: number }) {
|
|
8
|
+
const simulateNetwork = () => sleep(options?.responseSpeed ?? 2000);
|
|
9
|
+
|
|
10
|
+
const john: CurrentUser = {
|
|
11
|
+
id: "john",
|
|
12
|
+
email: "john@example.com",
|
|
13
|
+
isVerified: true,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const mary: CurrentUser = {
|
|
17
|
+
id: "mary",
|
|
18
|
+
email: "mary@example.com",
|
|
19
|
+
isVerified: true,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const vernon: CurrentUser = {
|
|
23
|
+
id: "vernon",
|
|
24
|
+
email: "vernon@example.com",
|
|
25
|
+
isVerified: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const sessionInfo: SessionInfo = {
|
|
29
|
+
createdTs: 123,
|
|
30
|
+
expiresTs: 123,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
data: { john, mary, vernon },
|
|
35
|
+
handlers: {
|
|
36
|
+
refreshTokens: {
|
|
37
|
+
success: () => {
|
|
38
|
+
return http.post(
|
|
39
|
+
"http://mock.api/v1/sessions/access-tokens",
|
|
40
|
+
async () => {
|
|
41
|
+
await simulateNetwork();
|
|
42
|
+
return HttpResponse.json({ sessionInfo });
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
failed: () => {
|
|
48
|
+
return http.post(
|
|
49
|
+
"http://mock.api/v1/sessions/access-tokens",
|
|
50
|
+
async () => {
|
|
51
|
+
await simulateNetwork();
|
|
52
|
+
return HttpResponse.text("Refresh token expired or missing", {
|
|
53
|
+
status: 401,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
getCurrentUser: {
|
|
61
|
+
success: (currentUser: CurrentUser) => {
|
|
62
|
+
return http.get("http://mock.api/v1/users/me", async () => {
|
|
63
|
+
await simulateNetwork();
|
|
64
|
+
return HttpResponse.json(currentUser);
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Cookie is valid, but user doesn't exist any more. This can happen
|
|
70
|
+
* after user deletion.
|
|
71
|
+
*/
|
|
72
|
+
notFound: () => {
|
|
73
|
+
return http.get("http://mock.api/v1/users/me", async () => {
|
|
74
|
+
await simulateNetwork();
|
|
75
|
+
return HttpResponse.text("User not found", { status: 404 });
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
noConnection: () => {
|
|
80
|
+
return http.get("http://mock.api/v1/users/me", async () => {
|
|
81
|
+
return HttpResponse.error();
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
unknownFailure: () => {
|
|
86
|
+
return http.get("http://mock.api/v1/users/me", async () => {
|
|
87
|
+
await simulateNetwork();
|
|
88
|
+
return HttpResponse.text("Internal server error", { status: 500 });
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Auth cookies no longer valid to make this request.
|
|
94
|
+
*/
|
|
95
|
+
notAuthenticated: () => {
|
|
96
|
+
return http.get("http://mock.api/v1/users/me", async () => {
|
|
97
|
+
await simulateNetwork();
|
|
98
|
+
return HttpResponse.text("Not authenticated", { status: 401 });
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
createNewSession: {
|
|
104
|
+
success: (currentUser: CurrentUser) => {
|
|
105
|
+
return http.post("http://mock.api/v1/sessions", async () => {
|
|
106
|
+
await simulateNetwork();
|
|
107
|
+
return HttpResponse.json({ currentUser, sessionInfo });
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
invalidCredentials: () => {
|
|
112
|
+
return http.post("http://mock.api/v1/sessions", async () => {
|
|
113
|
+
await simulateNetwork();
|
|
114
|
+
return HttpResponse.text("Credentials do not match", {
|
|
115
|
+
status: 401,
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
userNotFound: () => {
|
|
121
|
+
return http.post("http://mock.api/v1/sessions", async () => {
|
|
122
|
+
await simulateNetwork();
|
|
123
|
+
return HttpResponse.text("User not found", { status: 404 });
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
unknownFailure: () => {
|
|
128
|
+
return http.post("http://mock.api/v1/sessions", async () => {
|
|
129
|
+
await simulateNetwork();
|
|
130
|
+
return HttpResponse.text("Internal server error", { status: 500 });
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
requestVerify: {
|
|
136
|
+
success: () => {
|
|
137
|
+
return http.post(
|
|
138
|
+
"http://mock.api/v1/user-verification-tokens",
|
|
139
|
+
async () => {
|
|
140
|
+
await simulateNetwork();
|
|
141
|
+
return HttpResponse.json({ message: "OK", tokenId: "1" });
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
verify: {
|
|
148
|
+
success: () => {
|
|
149
|
+
return http.put(
|
|
150
|
+
"http://mock.api/v1/user-verification-tokens/:id",
|
|
151
|
+
async () => {
|
|
152
|
+
await simulateNetwork();
|
|
153
|
+
return HttpResponse.json({ message: "OK" });
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const { data, handlers } = createMocks({ responseSpeed: 700 });
|
|
163
|
+
|
|
164
|
+
const meta = preview.meta({
|
|
165
|
+
title: "Account/Current User Fetcher",
|
|
166
|
+
component: CurrentUserFetcher,
|
|
167
|
+
tags: ["autodocs"],
|
|
168
|
+
args: {
|
|
169
|
+
children: (
|
|
170
|
+
<div
|
|
171
|
+
style={{
|
|
172
|
+
minBlockSize: "20rem",
|
|
173
|
+
backgroundColor: "white",
|
|
174
|
+
display: "flex",
|
|
175
|
+
alignItems: "center",
|
|
176
|
+
justifyContent: "center",
|
|
177
|
+
borderRadius: "1rem",
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
App content
|
|
181
|
+
</div>
|
|
182
|
+
),
|
|
183
|
+
},
|
|
184
|
+
parameters: {
|
|
185
|
+
msw: {
|
|
186
|
+
handlers: {
|
|
187
|
+
refreshTokens: handlers.refreshTokens.failed(),
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The default case, in which a local user is provided (so they have previously
|
|
195
|
+
* logged in), and the current user request returns the same user as is
|
|
196
|
+
* the local user (determined by the user id).
|
|
197
|
+
*/
|
|
198
|
+
export const Default = meta.story({
|
|
199
|
+
parameters: {
|
|
200
|
+
msw: {
|
|
201
|
+
handlers: {
|
|
202
|
+
getCurrentUser: handlers.getCurrentUser.success(data.john),
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* In this case, no local user is provided and the component simply renders
|
|
210
|
+
* its children. There should be no network request in this case.
|
|
211
|
+
*/
|
|
212
|
+
export const NoLocalUser = meta.story({});
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* In this case, the local user is provided and the current user request returns
|
|
216
|
+
* error 401. The user should supply their credentials again.
|
|
217
|
+
*/
|
|
218
|
+
export const SessionExpired = meta.story({
|
|
219
|
+
parameters: {
|
|
220
|
+
msw: {
|
|
221
|
+
handlers: {
|
|
222
|
+
getCurrentUser: handlers.getCurrentUser.notAuthenticated(),
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* In this case, the local user is provided and the current user request returns
|
|
230
|
+
* a different user (determined by user ID).
|
|
231
|
+
*
|
|
232
|
+
* This case can happen when user A logs into app X, then user B logs into
|
|
233
|
+
* app Y. Returning to app X, user B will be logged into ITC with their account
|
|
234
|
+
* but local data belong to user A.
|
|
235
|
+
*
|
|
236
|
+
* In practice this should be a rare case, but with unpleasant circumstances
|
|
237
|
+
* if not handled correctly.
|
|
238
|
+
*/
|
|
239
|
+
export const UserMismatch = meta.story({
|
|
240
|
+
parameters: {
|
|
241
|
+
msw: {
|
|
242
|
+
handlers: {
|
|
243
|
+
getCurrentUser: handlers.getCurrentUser.success(data.mary),
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
*/
|
|
251
|
+
export const UserUnverified = meta.story({
|
|
252
|
+
parameters: {
|
|
253
|
+
msw: {
|
|
254
|
+
handlers: {
|
|
255
|
+
getCurrentUser: handlers.getCurrentUser.success(data.vernon),
|
|
256
|
+
requestVerify: handlers.requestVerify.success(),
|
|
257
|
+
verify: handlers.verify.success(),
|
|
258
|
+
refreshTokens: handlers.refreshTokens.success(),
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* In this case, the local user is provided and the current user request returns
|
|
266
|
+
* 404, indicating that the current user no longer exists.
|
|
267
|
+
*
|
|
268
|
+
* This can happen if users delete their accounts but
|
|
269
|
+
*/
|
|
270
|
+
export const UserNotFound = meta.story({
|
|
271
|
+
parameters: {
|
|
272
|
+
msw: {
|
|
273
|
+
handlers: {
|
|
274
|
+
getCurrentUser: handlers.getCurrentUser.notFound(),
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The proactive user session check has failed due to an error that doesn't
|
|
282
|
+
* carry any special meaning.
|
|
283
|
+
*/
|
|
284
|
+
export const UnknownFailure = meta.story({
|
|
285
|
+
parameters: {
|
|
286
|
+
msw: {
|
|
287
|
+
handlers: {
|
|
288
|
+
getCurrentUser: handlers.getCurrentUser.unknownFailure(),
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { useState, type ReactNode } from "react";
|
|
2
|
+
import { ModalDialog } from "../ModalDialog/index.tsx";
|
|
3
|
+
import { useCurrentUser } from "../store/index.tsx";
|
|
4
|
+
import { AccountIssueView } from "./AccountIssueView.tsx";
|
|
5
|
+
import { LoginView } from "./LoginView.tsx";
|
|
6
|
+
import { useFetchCurrentUser } from "./useFetchCurrentUser.tsx";
|
|
7
|
+
import { UserMismatchView } from "./UserMismatchView.tsx";
|
|
8
|
+
import { VerifyAccountView } from "./VerifyPage.tsx";
|
|
9
|
+
|
|
10
|
+
type CurrentUserFetcherProps = {
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Fetches fresh current user data if a local user exists in the app store.
|
|
16
|
+
*
|
|
17
|
+
* This component uses the Indie Tabletop Client under the hood, so if new
|
|
18
|
+
* data is successfully fetched, the onCurrentUser callback will be invoked,
|
|
19
|
+
* and it is up to the configuration of the client to store the data.
|
|
20
|
+
*
|
|
21
|
+
* Importantly, this component also handles the various user account issues
|
|
22
|
+
* that we could run into: expired session, user mismatch and account deletion.
|
|
23
|
+
*
|
|
24
|
+
* All other errors are ignored. This allows users to use the app in offline
|
|
25
|
+
* mode, and doesn't interrupt their session if some unexpected error happens,
|
|
26
|
+
* which they cannot do anything about anyways.
|
|
27
|
+
*/
|
|
28
|
+
export function CurrentUserFetcher(props: CurrentUserFetcherProps) {
|
|
29
|
+
const { children } = props;
|
|
30
|
+
const localUser = useCurrentUser();
|
|
31
|
+
const [isOpen, setOpen] = useState(true);
|
|
32
|
+
|
|
33
|
+
const { result, reload } = useFetchCurrentUser({
|
|
34
|
+
// We only want to fetch the current user if they exist in the store, i.e.
|
|
35
|
+
// they have logged into the app previously.
|
|
36
|
+
performFetch: !!localUser,
|
|
37
|
+
|
|
38
|
+
// If we are performing a fetch, we want to make sure that it is up to date
|
|
39
|
+
// every time the user focuses the window.
|
|
40
|
+
revalidateOnFocus: true,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (result.isFailure && result.failure.type === "API_ERROR") {
|
|
44
|
+
// The user's session has expired. They should be prompted to
|
|
45
|
+
// re-authenticate, as any syncing attemps will fail.
|
|
46
|
+
if (result.failure.code === 401) {
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<ModalDialog size="large" open>
|
|
50
|
+
<LoginView
|
|
51
|
+
currentUser={localUser}
|
|
52
|
+
onLogin={() => reload()}
|
|
53
|
+
description={undefined}
|
|
54
|
+
reload={reload}
|
|
55
|
+
/>
|
|
56
|
+
</ModalDialog>
|
|
57
|
+
{children}
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (result.failure.code === 404) {
|
|
63
|
+
// The user account is not found. The user might have been deleted.
|
|
64
|
+
// The user should be notified and instructed to log out, as many
|
|
65
|
+
// interactions acrosss the app will be broken.
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
<ModalDialog size="large" open>
|
|
69
|
+
<AccountIssueView reload={reload} />
|
|
70
|
+
</ModalDialog>
|
|
71
|
+
|
|
72
|
+
{children}
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (localUser && result.isSuccess) {
|
|
79
|
+
const serverUser = result.value;
|
|
80
|
+
|
|
81
|
+
// The cookie (server) user and the user in local storage are a different
|
|
82
|
+
// user. The current user needs to decide which account to use.
|
|
83
|
+
if (serverUser.id !== localUser.id) {
|
|
84
|
+
return (
|
|
85
|
+
<>
|
|
86
|
+
<ModalDialog size="large" open>
|
|
87
|
+
<UserMismatchView
|
|
88
|
+
serverUser={result.value}
|
|
89
|
+
localUser={localUser}
|
|
90
|
+
reload={reload}
|
|
91
|
+
/>
|
|
92
|
+
</ModalDialog>
|
|
93
|
+
|
|
94
|
+
{children}
|
|
95
|
+
</>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!serverUser.isVerified) {
|
|
100
|
+
return (
|
|
101
|
+
<>
|
|
102
|
+
<ModalDialog size="large" open={isOpen}>
|
|
103
|
+
<VerifyAccountView
|
|
104
|
+
currentUser={serverUser}
|
|
105
|
+
onClose={() => setOpen(false)}
|
|
106
|
+
reload={reload}
|
|
107
|
+
/>
|
|
108
|
+
</ModalDialog>
|
|
109
|
+
|
|
110
|
+
{children}
|
|
111
|
+
</>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// In all other cases we simply render the children
|
|
117
|
+
return <>{children}</>;
|
|
118
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Button } from "@ariakit/react";
|
|
2
|
+
import { cx } from "../class-names.ts";
|
|
3
|
+
import { interactiveText } from "../common.css.ts";
|
|
4
|
+
import {
|
|
5
|
+
Letterhead,
|
|
6
|
+
LetterheadHeading,
|
|
7
|
+
LetterheadParagraph,
|
|
8
|
+
} from "../Letterhead/index.tsx";
|
|
9
|
+
|
|
10
|
+
export function FailureFallbackView() {
|
|
11
|
+
return (
|
|
12
|
+
<Letterhead>
|
|
13
|
+
<LetterheadHeading>Something went wrong</LetterheadHeading>
|
|
14
|
+
|
|
15
|
+
<LetterheadParagraph>
|
|
16
|
+
{"This is probably an issue on our side. Sorry about that!"}
|
|
17
|
+
</LetterheadParagraph>
|
|
18
|
+
|
|
19
|
+
<LetterheadParagraph>
|
|
20
|
+
{"You can try "}
|
|
21
|
+
<Button
|
|
22
|
+
{...cx(interactiveText)}
|
|
23
|
+
onClick={() => window.location.reload()}
|
|
24
|
+
>
|
|
25
|
+
reloading the app
|
|
26
|
+
</Button>
|
|
27
|
+
{". "}
|
|
28
|
+
{"If the issue persists, please get in touch at "}
|
|
29
|
+
<a {...cx(interactiveText)} href="mailto:support@indietabletop.club">
|
|
30
|
+
support@indietabletop.club
|
|
31
|
+
</a>
|
|
32
|
+
{"."}
|
|
33
|
+
</LetterheadParagraph>
|
|
34
|
+
</Letterhead>
|
|
35
|
+
);
|
|
36
|
+
}
|