@protobase/ui 0.14.7 → 0.15.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.
- package/dist/app/app.d.ts +4 -4
- package/dist/app/app.js +29 -27
- package/dist/app/record-page.js +1 -1
- package/dist/app/search/use-global-search.js +1 -1
- package/dist/app/use-app-address.js +1 -1
- package/dist/app-shell/sidebar.js +7 -3
- package/dist/auth/auth-gate.js +97 -43
- package/dist/auth/auth-provider.js +0 -13
- package/dist/auth/sign-in-address.js +47 -0
- package/dist/auth/sign-in-prompt.js +5 -3
- package/dist/auth/sign-up-page.js +6 -10
- package/dist/auth/use-sign-in-address.js +27 -0
- package/dist/package.js +1 -1
- package/dist/shell/assets/app-CifGg2Cp.js +66 -0
- package/dist/shell/assets/{index-CeNa43oR.js → index-FlLqQ6zV.js} +1 -1
- package/dist/shell/assets/{maplibre-gl-cPRHcP5-.js → maplibre-gl-CCmop27r.js} +1 -1
- package/dist/shell/assets/{record-map-CEX9WoXA.js → record-map-m3iQ-yXN.js} +2 -2
- package/dist/shell/assets/{records-map-B3c6f2Lz.js → records-map-BYBf20Qw.js} +1 -1
- package/dist/shell/assets/{shared-protobase-ui-C1Dht33B.js → shared-protobase-ui-CycXFo_n.js} +1 -1
- package/dist/shell/index.html +3 -3
- package/dist/shell/protobase.shell.json +1 -1
- package/package.json +4 -4
- package/dist/shell/assets/app-BCo2kC5x.js +0 -66
package/dist/app/app.d.ts
CHANGED
|
@@ -29,9 +29,9 @@ export type AppProps = {
|
|
|
29
29
|
pluginLoader?: PluginLoader;
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
32
|
-
* The admin: sign-in first, or browsing as a visitor when the project has an anonymous role,
|
|
33
|
-
* `/meta`, `/:page` composed pages, `/:resource` lists and `/:resource/:key` records. Fills its
|
|
34
|
-
* this browser chose, light, dark or the system's.
|
|
32
|
+
* The admin: the sign-in route first, `/-/sign-in`, or browsing as a visitor when the project has an anonymous role,
|
|
33
|
+
* then a sidebar from `/meta`, `/:page` composed pages, `/:resource` lists and `/:resource/:key` records. Fills its
|
|
34
|
+
* parent, in the theme this browser chose, light, dark or the system's.
|
|
35
35
|
*/
|
|
36
|
-
export declare const App: ({ auth, workspace, ...rest }: AppProps) => import("react").JSX.Element;
|
|
36
|
+
export declare const App: ({ auth, workspace, basePath, initialUrl, ...rest }: AppProps) => import("react").JSX.Element;
|
|
37
37
|
//#endregion
|
package/dist/app/app.js
CHANGED
|
@@ -4,6 +4,8 @@ import { AuthProvider, useAuth } from "../auth/auth-provider.js";
|
|
|
4
4
|
import { AccountSecurityPage } from "../account/account-security-page.js";
|
|
5
5
|
import { SignInPolicyPage } from "../account/sign-in-policy-page.js";
|
|
6
6
|
import { ApiProvider } from "../data/api-provider.js";
|
|
7
|
+
import { Router, useRouter } from "./router.js";
|
|
8
|
+
import { useSignInAddress } from "../auth/use-sign-in-address.js";
|
|
7
9
|
import { managesOrganization, organizationMenuItems, organizationPageTitles } from "../organizations/organization-paths.js";
|
|
8
10
|
import { AuthGate } from "../auth/auth-gate.js";
|
|
9
11
|
import { isAdmin, userToShell } from "../auth/user-to-shell.js";
|
|
@@ -19,7 +21,6 @@ import { bottomBarFromMeta } from "./bottom-bar-from-meta.js";
|
|
|
19
21
|
import { breadcrumbFor } from "./breadcrumb-for.js";
|
|
20
22
|
import { sourceStatusOf } from "../data/with-sources.js";
|
|
21
23
|
import { MetaGate, Notice, useAdminMeta } from "./meta-gate.js";
|
|
22
|
-
import { Router, useRouter } from "./router.js";
|
|
23
24
|
import { useAppAddress } from "./use-app-address.js";
|
|
24
25
|
import { CustomBoundary } from "./pages/custom-boundary.js";
|
|
25
26
|
import { CreatePage } from "./create/create-page.js";
|
|
@@ -49,7 +50,8 @@ const placeOf = (address) => address?.kind === "page" ? address.page : address?.
|
|
|
49
50
|
const recordOf = (address) => address?.kind === "record" ? address : void 0;
|
|
50
51
|
const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
|
|
51
52
|
const meta = useAdminMeta();
|
|
52
|
-
const { state, session, signOut,
|
|
53
|
+
const { state, session, signOut, stopStaffSession, organizations: organizationSettings } = useAuth();
|
|
54
|
+
const signIn = useSignInAddress();
|
|
53
55
|
if (state.kind !== "signed-in" && state.kind !== "visitor") throw new Error("The shell is only rendered for a signed-in user or a visitor");
|
|
54
56
|
const user = state.kind === "signed-in" ? state.user : void 0;
|
|
55
57
|
const staff = state.kind === "signed-in" ? state.staff : void 0;
|
|
@@ -172,7 +174,7 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
|
|
|
172
174
|
breadcrumb,
|
|
173
175
|
user: acting && userToShell(acting),
|
|
174
176
|
onSignOut: user && (() => void signOut()),
|
|
175
|
-
onSignIn: user ? void 0 :
|
|
177
|
+
onSignIn: user ? void 0 : () => signIn.open(),
|
|
176
178
|
workspace: current?.organization?.name ?? workspace,
|
|
177
179
|
navGroups: groups,
|
|
178
180
|
bottomBar: bottomBarFromMeta(meta, basePath, place),
|
|
@@ -189,7 +191,7 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
|
|
|
189
191
|
others: organizations.organizations.filter((organization) => organization.id !== current?.organization?.id),
|
|
190
192
|
onSwitch: (id) => void session.organizations.switchTo(id).then(() => reopenIn(basePath))
|
|
191
193
|
},
|
|
192
|
-
theme:
|
|
194
|
+
theme: {
|
|
193
195
|
preference: theme.preference,
|
|
194
196
|
onChange: theme.choose
|
|
195
197
|
},
|
|
@@ -241,7 +243,7 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
|
|
|
241
243
|
]
|
|
242
244
|
});
|
|
243
245
|
};
|
|
244
|
-
const Authenticated = ({ baseUrl, client, sources,
|
|
246
|
+
const Authenticated = ({ baseUrl, client, sources, workspace, sidebarMode, ui, assistant, runtime, pluginLoader }) => {
|
|
245
247
|
const { state, session, markSignedOut } = useAuth();
|
|
246
248
|
return /* @__PURE__ */ jsx(ApiProvider, {
|
|
247
249
|
baseUrl,
|
|
@@ -249,34 +251,34 @@ const Authenticated = ({ baseUrl, client, sources, basePath, initialUrl, workspa
|
|
|
249
251
|
sources,
|
|
250
252
|
token: state.kind === "visitor" ? void 0 : session.token,
|
|
251
253
|
onUnauthenticated: markSignedOut,
|
|
252
|
-
children: /* @__PURE__ */ jsx(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
children: /* @__PURE__ */ jsx(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
runtime
|
|
263
|
-
})
|
|
264
|
-
}) }) })
|
|
265
|
-
})
|
|
254
|
+
children: /* @__PURE__ */ jsx(MetaGate, { children: /* @__PURE__ */ jsx(ToastProvider, { children: /* @__PURE__ */ jsx(PluginGate, {
|
|
255
|
+
ui,
|
|
256
|
+
...pluginLoader && { loader: pluginLoader },
|
|
257
|
+
children: /* @__PURE__ */ jsx(Routes, {
|
|
258
|
+
workspace,
|
|
259
|
+
sidebarMode,
|
|
260
|
+
assistant,
|
|
261
|
+
runtime
|
|
262
|
+
})
|
|
263
|
+
}) }) })
|
|
266
264
|
});
|
|
267
265
|
};
|
|
268
266
|
/**
|
|
269
|
-
* The admin: sign-in first, or browsing as a visitor when the project has an anonymous role,
|
|
270
|
-
* `/meta`, `/:page` composed pages, `/:resource` lists and `/:resource/:key` records. Fills its
|
|
271
|
-
* this browser chose, light, dark or the system's.
|
|
267
|
+
* The admin: the sign-in route first, `/-/sign-in`, or browsing as a visitor when the project has an anonymous role,
|
|
268
|
+
* then a sidebar from `/meta`, `/:page` composed pages, `/:resource` lists and `/:resource/:key` records. Fills its
|
|
269
|
+
* parent, in the theme this browser chose, light, dark or the system's.
|
|
272
270
|
*/
|
|
273
|
-
const App = ({ auth, workspace, ...rest }) => /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(AuthProvider, {
|
|
271
|
+
const App = ({ auth, workspace, basePath, initialUrl, ...rest }) => /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(AuthProvider, {
|
|
274
272
|
session: auth,
|
|
275
|
-
children: /* @__PURE__ */ jsx(
|
|
276
|
-
|
|
277
|
-
|
|
273
|
+
children: /* @__PURE__ */ jsx(Router, {
|
|
274
|
+
basePath,
|
|
275
|
+
initialUrl,
|
|
276
|
+
children: /* @__PURE__ */ jsx(AuthGate, {
|
|
278
277
|
workspace,
|
|
279
|
-
|
|
278
|
+
children: /* @__PURE__ */ jsx(Authenticated, {
|
|
279
|
+
workspace,
|
|
280
|
+
...rest
|
|
281
|
+
})
|
|
280
282
|
})
|
|
281
283
|
})
|
|
282
284
|
}) });
|
package/dist/app/record-page.js
CHANGED
|
@@ -3,13 +3,13 @@ import { formatRelativeTime } from "../format/date.js";
|
|
|
3
3
|
import { Button } from "../primitives/button.js";
|
|
4
4
|
import { keys } from "../data/query-keys.js";
|
|
5
5
|
import { useClient } from "../data/api-provider.js";
|
|
6
|
+
import { useNavigationGuard, useRouter } from "./router.js";
|
|
6
7
|
import { useRecord } from "../data/use-record.js";
|
|
7
8
|
import { recordTitle } from "../live/model-helpers.js";
|
|
8
9
|
import { humanize } from "../live/naming.js";
|
|
9
10
|
import { useProjectUi } from "./pages/project-ui.js";
|
|
10
11
|
import { displayValue } from "../live/display-value.js";
|
|
11
12
|
import { Notice, useAdminMeta, usePermissions } from "./meta-gate.js";
|
|
12
|
-
import { useNavigationGuard, useRouter } from "./router.js";
|
|
13
13
|
import { useAppAddress } from "./use-app-address.js";
|
|
14
14
|
import { ErrorBanner } from "./error-banner.js";
|
|
15
15
|
import { shownWidget } from "./field-types/shown-widget.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useClient } from "../../data/api-provider.js";
|
|
2
|
-
import { useAdminMeta } from "../meta-gate.js";
|
|
3
2
|
import { useRouter } from "../router.js";
|
|
3
|
+
import { useAdminMeta } from "../meta-gate.js";
|
|
4
4
|
import { useDebounced } from "../../lib/use-debounced.js";
|
|
5
5
|
import { searchFields, searchFilter, searchPlaceholder, searchTargets, toHit } from "./search-model.js";
|
|
6
6
|
import { useMemo, useState } from "react";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useIsXs } from "../lib/use-media-query.js";
|
|
2
|
+
import { Link, useRouter } from "./router.js";
|
|
2
3
|
import { changedList, currentAddress, listDefaults, listedResource } from "./current-address.js";
|
|
3
4
|
import { useAdminMeta } from "./meta-gate.js";
|
|
4
|
-
import { Link, useRouter } from "./router.js";
|
|
5
5
|
import { useCallback, useMemo } from "react";
|
|
6
6
|
import { jsx } from "react/jsx-runtime";
|
|
7
7
|
import { appPath, readAppPath } from "@protobase/schema";
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { Logo } from "./logo.js";
|
|
3
3
|
import { navGroups } from "./nav.js";
|
|
4
|
+
import { ThemeSwitch } from "./theme-switch.js";
|
|
4
5
|
import { ProfileMenu } from "./profile-menu.js";
|
|
5
6
|
import { isTextMode, sidebarWidth } from "./sidebar-mode.js";
|
|
6
7
|
import { SidebarItem } from "./sidebar-item.js";
|
|
7
8
|
import { SidebarGroup } from "./sidebar-group.js";
|
|
8
9
|
import { LogIn } from "lucide-react";
|
|
9
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
11
|
//#region src/app-shell/sidebar.tsx
|
|
11
12
|
const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenuOpen, groups = navGroups, userMenu, onNavigate, onSignOut, onSignIn, organizations, roles, theme, sticky }) => {
|
|
12
13
|
const expanding = mode === "icon-expand";
|
|
@@ -58,7 +59,10 @@ const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenu
|
|
|
58
59
|
organizations,
|
|
59
60
|
roles,
|
|
60
61
|
theme
|
|
61
|
-
}) :
|
|
62
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [theme && inline && /* @__PURE__ */ jsx(ThemeSwitch, {
|
|
63
|
+
theme,
|
|
64
|
+
className: "flex min-h-11 w-full items-center gap-2.5 whitespace-nowrap p-1.5 text-[13px] text-muted-foreground md:min-h-0"
|
|
65
|
+
}), onSignIn && /* @__PURE__ */ jsxs("button", {
|
|
62
66
|
type: "button",
|
|
63
67
|
onClick: onSignIn,
|
|
64
68
|
"aria-label": "Sign in",
|
|
@@ -67,7 +71,7 @@ const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenu
|
|
|
67
71
|
className: "whitespace-nowrap",
|
|
68
72
|
children: "Sign in"
|
|
69
73
|
})]
|
|
70
|
-
})
|
|
74
|
+
})] })
|
|
71
75
|
})
|
|
72
76
|
]
|
|
73
77
|
})
|
package/dist/auth/auth-gate.js
CHANGED
|
@@ -3,6 +3,7 @@ import { providerMessage } from "./auth-messages.js";
|
|
|
3
3
|
import { readProviderError, readSignInLink, withoutSignInLink } from "./provider-link.js";
|
|
4
4
|
import { useAuth } from "./auth-provider.js";
|
|
5
5
|
import { Button } from "../primitives/button.js";
|
|
6
|
+
import { useRouter } from "../app/router.js";
|
|
6
7
|
import { EmailCodePage } from "./email-code-page.js";
|
|
7
8
|
import { FirstRunPage } from "./first-run-page.js";
|
|
8
9
|
import { readResetLink, withoutResetLink } from "./reset-link.js";
|
|
@@ -10,11 +11,13 @@ import { ForgotPasswordPage } from "./forgot-password-page.js";
|
|
|
10
11
|
import { ProviderSignInPage } from "./provider-sign-in-page.js";
|
|
11
12
|
import { ResetPasswordPage } from "./reset-password-page.js";
|
|
12
13
|
import { SetupRequiredPage } from "./setup-required-page.js";
|
|
14
|
+
import { signInPath } from "./sign-in-address.js";
|
|
13
15
|
import { SignInPage } from "./sign-in-page.js";
|
|
14
16
|
import { SignUpPage } from "./sign-up-page.js";
|
|
15
17
|
import { readStaffLink, withoutStaffLink } from "./staff-link.js";
|
|
16
18
|
import { StaffSignInPage } from "./staff-sign-in-page.js";
|
|
17
19
|
import { TwoFactorPage } from "./two-factor-page.js";
|
|
20
|
+
import { useSignInAddress } from "./use-sign-in-address.js";
|
|
18
21
|
import { readInvitationLink, withoutInvitationLink } from "../organizations/invitation-link.js";
|
|
19
22
|
import { InvitationPage } from "../organizations/invitation-page.js";
|
|
20
23
|
import { useCallback, useEffect, useState } from "react";
|
|
@@ -35,8 +38,7 @@ const firstPage = () => {
|
|
|
35
38
|
const invitation = readInvitationLink(href);
|
|
36
39
|
if (invitation) return {
|
|
37
40
|
kind: "invitation",
|
|
38
|
-
token: invitation.token
|
|
39
|
-
signingIn: false
|
|
41
|
+
token: invitation.token
|
|
40
42
|
};
|
|
41
43
|
const provider = readSignInLink(href);
|
|
42
44
|
if (provider) return {
|
|
@@ -49,33 +51,62 @@ const firstPage = () => {
|
|
|
49
51
|
providerError
|
|
50
52
|
} : { kind: "sign-in" };
|
|
51
53
|
};
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
54
|
+
/** Moves to `to`, below the base path, in place of this page in the tab's history, as soon as it shows. */
|
|
55
|
+
const Redirect = ({ to }) => {
|
|
56
|
+
const { navigate } = useRouter();
|
|
57
|
+
useEffect(() => navigate(to, { replace: true }), [navigate, to]);
|
|
58
|
+
return null;
|
|
59
|
+
};
|
|
56
60
|
/**
|
|
57
61
|
* Shows the app when the user is signed in (or the server has no login), or to a visitor when the project lets them
|
|
58
|
-
* browse, and the right page otherwise: the sign-in
|
|
59
|
-
*
|
|
62
|
+
* browse, and the right page otherwise: the step of the sign-in route the address names, or the page an emailed reset
|
|
63
|
+
* link, an invitation link or a staff link opened. Someone signed out without a visitor's browsing goes to the sign-in
|
|
64
|
+
* route, and signing in comes back to the page the route was opened from. An invited person with an account signs in
|
|
65
|
+
* first and comes back to the invitation.
|
|
60
66
|
*/
|
|
61
67
|
const AuthGate = ({ workspace, children }) => {
|
|
62
|
-
const { state, recheck, passwordReset, signOut, signInProviders,
|
|
68
|
+
const { state, recheck, passwordReset, signOut, signInProviders, canSignUp, browse } = useAuth();
|
|
69
|
+
const { path, params, basePath, navigate } = useRouter();
|
|
70
|
+
const { address, next, open, leave } = useSignInAddress();
|
|
63
71
|
const [page, setPage] = useState(firstPage);
|
|
72
|
+
const [codeEmail, setCodeEmail] = useState();
|
|
73
|
+
const [signUpEmail, setSignUpEmail] = useState();
|
|
74
|
+
const href = new URL(`${basePath}${path}${params.size > 0 ? `?${params}` : ""}`, window.location.origin).href;
|
|
75
|
+
const forget = useCallback((without) => {
|
|
76
|
+
const below = without(href).slice(basePath.length);
|
|
77
|
+
navigate(below.startsWith("/") ? below : `/${below}`, { replace: true });
|
|
78
|
+
}, [
|
|
79
|
+
href,
|
|
80
|
+
basePath,
|
|
81
|
+
navigate
|
|
82
|
+
]);
|
|
83
|
+
const entered = state.kind === "signed-in" || state.kind === "two-factor" || state.kind === "setup-required";
|
|
64
84
|
useEffect(() => {
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
if (!entered) return;
|
|
86
|
+
if (page.kind === "provider-sign-in") forget(withoutSignInLink);
|
|
87
|
+
if (page.kind === "provider-sign-in" || page.kind === "sign-in" && (page.notice || page.error || page.providerError)) setPage({ kind: "sign-in" });
|
|
88
|
+
setCodeEmail(void 0);
|
|
89
|
+
setSignUpEmail(void 0);
|
|
90
|
+
}, [
|
|
91
|
+
entered,
|
|
92
|
+
page,
|
|
93
|
+
forget
|
|
94
|
+
]);
|
|
95
|
+
const providerErrorLeft = address !== void 0 && readProviderError(href) !== void 0;
|
|
70
96
|
useEffect(() => {
|
|
71
|
-
if (
|
|
72
|
-
}, [
|
|
97
|
+
if (!entered && page.kind === "sign-in" && page.providerError && providerErrorLeft) forget(withoutSignInLink);
|
|
98
|
+
}, [
|
|
99
|
+
entered,
|
|
100
|
+
page,
|
|
101
|
+
providerErrorLeft,
|
|
102
|
+
forget
|
|
103
|
+
]);
|
|
73
104
|
const signInFailed = useCallback((error) => setPage({
|
|
74
105
|
kind: "sign-in",
|
|
75
106
|
error
|
|
76
107
|
}), []);
|
|
77
108
|
const leaveResetPage = (next) => {
|
|
78
|
-
|
|
109
|
+
forget(withoutResetLink);
|
|
79
110
|
setPage(next);
|
|
80
111
|
};
|
|
81
112
|
const passwordChanged = async () => {
|
|
@@ -87,7 +118,7 @@ const AuthGate = ({ workspace, children }) => {
|
|
|
87
118
|
};
|
|
88
119
|
const backToSignIn = () => {
|
|
89
120
|
setPage({ kind: "sign-in" });
|
|
90
|
-
signOut().then(
|
|
121
|
+
signOut().then(() => open("sign-in", { replace: true }));
|
|
91
122
|
};
|
|
92
123
|
switch (state.kind) {
|
|
93
124
|
case "loading": return /* @__PURE__ */ jsxs("div", {
|
|
@@ -106,36 +137,33 @@ const AuthGate = ({ workspace, children }) => {
|
|
|
106
137
|
});
|
|
107
138
|
case "needs-admin": return /* @__PURE__ */ jsx(FirstRunPage, { onCheckAgain: recheck });
|
|
108
139
|
}
|
|
140
|
+
if (state.kind === "signed-in" && address) return /* @__PURE__ */ jsx(Redirect, { to: next ?? "/" });
|
|
109
141
|
if (page.kind === "reset-password") return /* @__PURE__ */ jsx(ResetPasswordPage, {
|
|
110
142
|
token: page.token,
|
|
111
143
|
onDone: () => void passwordChanged(),
|
|
112
|
-
onRequestNewLink: passwordReset ? () =>
|
|
144
|
+
onRequestNewLink: passwordReset ? () => {
|
|
145
|
+
setPage({ kind: "sign-in" });
|
|
146
|
+
navigate(signInPath({ step: "forgot-password" }), { replace: true });
|
|
147
|
+
} : void 0,
|
|
113
148
|
onBack: () => leaveResetPage({ kind: "sign-in" })
|
|
114
149
|
});
|
|
115
150
|
if (page.kind === "staff-sign-in") return /* @__PURE__ */ jsx(StaffSignInPage, {
|
|
116
151
|
link: page.link,
|
|
117
152
|
onBack: () => {
|
|
118
|
-
|
|
153
|
+
forget(withoutStaffLink);
|
|
119
154
|
setPage({ kind: "sign-in" });
|
|
120
155
|
}
|
|
121
156
|
});
|
|
122
157
|
const signedOut = state.kind === "signed-out" || state.kind === "visitor";
|
|
123
|
-
if (page.kind === "invitation" && (state.kind === "signed-in" || signedOut && !
|
|
158
|
+
if (page.kind === "invitation" && (state.kind === "signed-in" || signedOut && !address)) return /* @__PURE__ */ jsx(InvitationPage, {
|
|
124
159
|
token: page.token,
|
|
125
160
|
onDone: () => {
|
|
126
|
-
|
|
161
|
+
forget(withoutInvitationLink);
|
|
127
162
|
setPage({ kind: "sign-in" });
|
|
128
163
|
},
|
|
129
|
-
onSignIn: () =>
|
|
130
|
-
setPage({
|
|
131
|
-
...page,
|
|
132
|
-
signingIn: true
|
|
133
|
-
});
|
|
134
|
-
showSignIn();
|
|
135
|
-
}
|
|
164
|
+
onSignIn: () => open()
|
|
136
165
|
});
|
|
137
166
|
if (state.kind === "signed-in") return children;
|
|
138
|
-
if (state.kind === "visitor" && page.kind === "sign-in" && !page.notice && !page.error && !page.providerError) return children;
|
|
139
167
|
if (state.kind === "two-factor") return /* @__PURE__ */ jsx(TwoFactorPage, {
|
|
140
168
|
methods: state.methods,
|
|
141
169
|
onBack: backToSignIn
|
|
@@ -146,26 +174,52 @@ const AuthGate = ({ workspace, children }) => {
|
|
|
146
174
|
workspace,
|
|
147
175
|
onFailed: signInFailed
|
|
148
176
|
});
|
|
149
|
-
if (page.kind === "forgot-password") return /* @__PURE__ */ jsx(ForgotPasswordPage, { onBack: () => setPage({ kind: "sign-in" }) });
|
|
150
|
-
if (page.kind === "email-code") return /* @__PURE__ */ jsx(EmailCodePage, {
|
|
151
|
-
email: page.email,
|
|
152
|
-
onBack: () => setPage({ kind: "sign-in" })
|
|
153
|
-
});
|
|
154
|
-
if (canSignUp && entry === "sign-up" && page.kind === "sign-in") return /* @__PURE__ */ jsx(SignUpPage, { workspace });
|
|
155
177
|
const signIn = page.kind === "sign-in" ? page : { kind: "sign-in" };
|
|
178
|
+
if (!address) {
|
|
179
|
+
const message = signIn.notice || signIn.error || signIn.providerError;
|
|
180
|
+
return state.kind === "visitor" && !message ? children : /* @__PURE__ */ jsx(Redirect, { to: signInPath({ next }) });
|
|
181
|
+
}
|
|
182
|
+
const step = {
|
|
183
|
+
"sign-in": true,
|
|
184
|
+
"forgot-password": passwordReset,
|
|
185
|
+
"email-code": codeEmail !== void 0,
|
|
186
|
+
"sign-up": canSignUp,
|
|
187
|
+
"sign-up-code": canSignUp && signUpEmail !== void 0
|
|
188
|
+
}[address.step] ? address.step : "sign-in";
|
|
189
|
+
const backTo = (target) => () => open(target, { replace: true });
|
|
190
|
+
const onBrowse = browse && (() => {
|
|
191
|
+
browse();
|
|
192
|
+
leave();
|
|
193
|
+
});
|
|
194
|
+
if (step === "forgot-password") return /* @__PURE__ */ jsx(ForgotPasswordPage, { onBack: backTo("sign-in") });
|
|
195
|
+
if (step === "email-code") return /* @__PURE__ */ jsx(EmailCodePage, {
|
|
196
|
+
email: codeEmail,
|
|
197
|
+
onBack: backTo("sign-in")
|
|
198
|
+
});
|
|
199
|
+
if (step === "sign-up" || step === "sign-up-code") return /* @__PURE__ */ jsx(SignUpPage, {
|
|
200
|
+
workspace,
|
|
201
|
+
email: step === "sign-up-code" ? signUpEmail : void 0,
|
|
202
|
+
onCodeSent: (email) => {
|
|
203
|
+
setSignUpEmail(email);
|
|
204
|
+
open("sign-up-code");
|
|
205
|
+
},
|
|
206
|
+
onBack: backTo("sign-up"),
|
|
207
|
+
onSignIn: () => open("sign-in"),
|
|
208
|
+
onBrowse
|
|
209
|
+
}, step);
|
|
156
210
|
const providerError = signIn.providerError && providerMessage(signIn.providerError, signInProviders.length === 1 ? signInProviders[0].name : "the provider");
|
|
157
211
|
const error = signIn.error ?? providerError;
|
|
158
212
|
return /* @__PURE__ */ jsx(SignInPage, {
|
|
159
213
|
workspace,
|
|
160
214
|
notice: signIn.notice,
|
|
161
215
|
error,
|
|
162
|
-
onForgotPassword: passwordReset ? () =>
|
|
163
|
-
onCodeSent: (email) =>
|
|
164
|
-
|
|
165
|
-
email
|
|
166
|
-
}
|
|
167
|
-
onSignUp: canSignUp ?
|
|
168
|
-
onBrowse
|
|
216
|
+
onForgotPassword: passwordReset ? () => open("forgot-password") : void 0,
|
|
217
|
+
onCodeSent: (email) => {
|
|
218
|
+
setCodeEmail(email);
|
|
219
|
+
open("email-code");
|
|
220
|
+
},
|
|
221
|
+
onSignUp: canSignUp ? () => open("sign-up") : void 0,
|
|
222
|
+
onBrowse
|
|
169
223
|
}, error);
|
|
170
224
|
};
|
|
171
225
|
//#endregion
|
|
@@ -15,7 +15,6 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
15
15
|
const [signInMethods, setSignInMethods] = useState(["password"]);
|
|
16
16
|
const [passwordReset, setPasswordReset] = useState(false);
|
|
17
17
|
const [canSignUp, setCanSignUp] = useState(false);
|
|
18
|
-
const [entry, setEntry] = useState("sign-in");
|
|
19
18
|
const [passkeyDomains, setPasskeyDomains] = useState([]);
|
|
20
19
|
const [socialProviders, setSocialProviders] = useState([]);
|
|
21
20
|
const [platformSignIn, setPlatformSignIn] = useState();
|
|
@@ -29,7 +28,6 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
29
28
|
const session = useMemo(() => given ?? createAuthSession({ onSignedOut: () => signedOut.current() }), [given]);
|
|
30
29
|
const enter = useCallback(async (user) => {
|
|
31
30
|
const [{ missing }, staff] = await Promise.all([session.account.signInMethods(), session.staff.current()]);
|
|
32
|
-
setEntry("sign-in");
|
|
33
31
|
setState(missing.length > 0 ? {
|
|
34
32
|
kind: "setup-required",
|
|
35
33
|
user,
|
|
@@ -71,7 +69,6 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
71
69
|
signInMethods,
|
|
72
70
|
passwordReset,
|
|
73
71
|
canSignUp,
|
|
74
|
-
entry,
|
|
75
72
|
passkeyDomains,
|
|
76
73
|
socialProviders,
|
|
77
74
|
signInProviders: socialProviders.map((id) => ({
|
|
@@ -82,16 +79,7 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
82
79
|
...organizations && { organizations },
|
|
83
80
|
markSignedOut,
|
|
84
81
|
recheck,
|
|
85
|
-
showSignIn: () => {
|
|
86
|
-
setEntry("sign-in");
|
|
87
|
-
setState((current) => current.kind === "visitor" ? { kind: "signed-out" } : current);
|
|
88
|
-
},
|
|
89
|
-
showSignUp: () => {
|
|
90
|
-
setEntry("sign-up");
|
|
91
|
-
setState((current) => current.kind === "visitor" ? { kind: "signed-out" } : current);
|
|
92
|
-
},
|
|
93
82
|
...anonymous && { browse: () => {
|
|
94
|
-
setEntry("sign-in");
|
|
95
83
|
setState((current) => current.kind === "signed-out" ? { kind: "visitor" } : current);
|
|
96
84
|
} },
|
|
97
85
|
signIn: async (email, password) => finish(await session.signIn(email, password)),
|
|
@@ -121,7 +109,6 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
121
109
|
signInMethods,
|
|
122
110
|
passwordReset,
|
|
123
111
|
canSignUp,
|
|
124
|
-
entry,
|
|
125
112
|
passkeyDomains,
|
|
126
113
|
socialProviders,
|
|
127
114
|
platformSignIn,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/auth/sign-in-address.ts
|
|
2
|
+
/** The path of the sign-in route, below the app's base path. Its steps and the page to come back to are in its query. */
|
|
3
|
+
const signInRoute = "/-/sign-in";
|
|
4
|
+
/** The names of the sign-in route's query parameters: the step it shows, and the page to come back to after it. */
|
|
5
|
+
const signInParams = {
|
|
6
|
+
step: "step",
|
|
7
|
+
next: "next"
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* A step of signing in or making an account, each with an address of its own: Back from a step returns to the one
|
|
11
|
+
* before. `email-code` and `sign-up-code` enter the code emailed to an address the step before asked for.
|
|
12
|
+
*/
|
|
13
|
+
const signInSteps = [
|
|
14
|
+
"sign-in",
|
|
15
|
+
"forgot-password",
|
|
16
|
+
"email-code",
|
|
17
|
+
"sign-up",
|
|
18
|
+
"sign-up-code"
|
|
19
|
+
];
|
|
20
|
+
const isStep = (value) => signInSteps.includes(value);
|
|
21
|
+
const localPage = (next) => {
|
|
22
|
+
if (!next?.startsWith("/")) return void 0;
|
|
23
|
+
const url = new URL(next, "http://local");
|
|
24
|
+
if (url.origin !== "http://local" || url.pathname === "/-/sign-in") return void 0;
|
|
25
|
+
return `${url.pathname}${url.search}`;
|
|
26
|
+
};
|
|
27
|
+
/** The path below the base path of a step of the sign-in route, coming back to `next` after it, the start by default. */
|
|
28
|
+
const signInPath = ({ step = "sign-in", next } = {}) => {
|
|
29
|
+
const params = new URLSearchParams();
|
|
30
|
+
if (step !== "sign-in") params.set(signInParams.step, step);
|
|
31
|
+
const page = localPage(next);
|
|
32
|
+
if (page && page !== "/") params.set(signInParams.next, page);
|
|
33
|
+
const query = params.toString();
|
|
34
|
+
return query ? `${signInRoute}?${query}` : signInRoute;
|
|
35
|
+
};
|
|
36
|
+
/** The sign-in route's step and the page to come back to, for the path below the base path; `undefined` elsewhere. */
|
|
37
|
+
const readSignInAddress = (path, params) => {
|
|
38
|
+
if (path !== "/-/sign-in") return void 0;
|
|
39
|
+
const step = params.get(signInParams.step);
|
|
40
|
+
const next = localPage(params.get(signInParams.next));
|
|
41
|
+
return {
|
|
42
|
+
step: isStep(step) ? step : "sign-in",
|
|
43
|
+
...next && { next }
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { readSignInAddress, signInParams, signInPath, signInRoute, signInSteps };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useAuth } from "./auth-provider.js";
|
|
2
2
|
import { Button, buttonClass } from "../primitives/button.js";
|
|
3
|
+
import { useSignInAddress } from "./use-sign-in-address.js";
|
|
3
4
|
import { namedIcon } from "../app/named-icon.js";
|
|
4
5
|
import { ResponsiveDialog } from "../primitives/responsive-dialog.js";
|
|
5
6
|
import { useId } from "react";
|
|
@@ -17,7 +18,8 @@ const Benefits = ({ benefits }) => benefits && benefits.length > 0 ? /* @__PURE_
|
|
|
17
18
|
}, benefit))
|
|
18
19
|
}) : null;
|
|
19
20
|
const Actions = ({ prompt }) => {
|
|
20
|
-
const {
|
|
21
|
+
const { canSignUp } = useAuth();
|
|
22
|
+
const { open } = useSignInAddress();
|
|
21
23
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [prompt.signUp ? /* @__PURE__ */ jsx("a", {
|
|
22
24
|
href: prompt.signUp.href,
|
|
23
25
|
target: "_blank",
|
|
@@ -25,11 +27,11 @@ const Actions = ({ prompt }) => {
|
|
|
25
27
|
className: buttonClass(),
|
|
26
28
|
children: prompt.signUp.label
|
|
27
29
|
}) : canSignUp && /* @__PURE__ */ jsx(Button, {
|
|
28
|
-
onClick:
|
|
30
|
+
onClick: () => open("sign-up"),
|
|
29
31
|
children: "Create an account"
|
|
30
32
|
}), /* @__PURE__ */ jsx(Button, {
|
|
31
33
|
variant: "primary",
|
|
32
|
-
onClick:
|
|
34
|
+
onClick: () => open(),
|
|
33
35
|
children: "Sign in"
|
|
34
36
|
})] });
|
|
35
37
|
};
|
|
@@ -10,9 +10,8 @@ import { jsx } from "react/jsx-runtime";
|
|
|
10
10
|
* Makes an account on a server with open sign-up: the address, then the code emailed to it with the person's name and
|
|
11
11
|
* password. The new account is signed in, and sets up what the sign-in policy requires as after any sign-in.
|
|
12
12
|
*/
|
|
13
|
-
const SignUpPage = ({ workspace }) => {
|
|
14
|
-
const { signInMethods, sendSignUpCode, signUp
|
|
15
|
-
const [email, setEmail] = useState();
|
|
13
|
+
const SignUpPage = ({ workspace, email, onCodeSent, onBack, onSignIn, onBrowse }) => {
|
|
14
|
+
const { signInMethods, sendSignUpCode, signUp } = useAuth();
|
|
16
15
|
const [busy, setBusy] = useState(false);
|
|
17
16
|
const [error, setError] = useState();
|
|
18
17
|
const [notice, setNotice] = useState();
|
|
@@ -32,11 +31,11 @@ const SignUpPage = ({ workspace }) => {
|
|
|
32
31
|
workspace,
|
|
33
32
|
busy,
|
|
34
33
|
error,
|
|
35
|
-
onSignIn
|
|
36
|
-
onBrowse
|
|
34
|
+
onSignIn,
|
|
35
|
+
onBrowse,
|
|
37
36
|
onSubmit: (address) => void attempt(async () => {
|
|
38
37
|
await sendSignUpCode(address);
|
|
39
|
-
|
|
38
|
+
onCodeSent(address);
|
|
40
39
|
})
|
|
41
40
|
});
|
|
42
41
|
return /* @__PURE__ */ jsx(SignUpDetailsForm, {
|
|
@@ -45,10 +44,7 @@ const SignUpPage = ({ workspace }) => {
|
|
|
45
44
|
busy,
|
|
46
45
|
error,
|
|
47
46
|
notice,
|
|
48
|
-
onBack
|
|
49
|
-
setError(void 0);
|
|
50
|
-
setEmail(void 0);
|
|
51
|
-
},
|
|
47
|
+
onBack,
|
|
52
48
|
onResend: () => void attempt(async () => {
|
|
53
49
|
await sendSignUpCode(email);
|
|
54
50
|
setNotice("A new code is on its way. Only the newest one works.");
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { withoutSignInLink } from "./provider-link.js";
|
|
2
|
+
import { useRouter } from "../app/router.js";
|
|
3
|
+
import { readSignInAddress, signInPath } from "./sign-in-address.js";
|
|
4
|
+
import { useCallback, useMemo } from "react";
|
|
5
|
+
//#region src/auth/use-sign-in-address.ts
|
|
6
|
+
/**
|
|
7
|
+
* The sign-in route as logical values, and the ways to move along it: `address` is the step it shows, or `undefined` on
|
|
8
|
+
* any other page; `next` is the page to come back to; `open` shows a step, and `leave` goes back to `next` in place of
|
|
9
|
+
* the route in the tab's history. Components read and write the sign-in route through this hook, never its query.
|
|
10
|
+
*/
|
|
11
|
+
const useSignInAddress = () => {
|
|
12
|
+
const { path, params, navigate } = useRouter();
|
|
13
|
+
const address = useMemo(() => readSignInAddress(path, params), [path, params]);
|
|
14
|
+
const here = withoutSignInLink(new URL(params.size > 0 ? `${path}?${params}` : path, "http://local").href);
|
|
15
|
+
const next = address ? address.next : here;
|
|
16
|
+
return {
|
|
17
|
+
address,
|
|
18
|
+
next,
|
|
19
|
+
open: useCallback((step = "sign-in", options) => navigate(signInPath({
|
|
20
|
+
step,
|
|
21
|
+
next
|
|
22
|
+
}), options), [navigate, next]),
|
|
23
|
+
leave: useCallback(() => navigate(next ?? "/", { replace: true }), [navigate, next])
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { useSignInAddress };
|
package/dist/package.js
CHANGED