@protobase/ui 0.14.6 → 0.15.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/dist/app/app.d.ts +4 -4
- package/dist/app/app.js +28 -26
- 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/auth/auth-gate.js +98 -41
- package/dist/auth/auth-provider.js +9 -2
- package/dist/auth/sign-in-address.js +47 -0
- package/dist/auth/sign-in-form.js +14 -1
- package/dist/auth/sign-in-page.js +2 -1
- package/dist/auth/sign-in-prompt.d.ts +2 -1
- package/dist/auth/sign-in-prompt.js +10 -4
- package/dist/auth/sign-up-details-form.js +130 -0
- package/dist/auth/sign-up-email-form.js +74 -0
- package/dist/auth/sign-up-page.js +59 -0
- package/dist/auth/use-sign-in-address.js +27 -0
- package/dist/package.js +1 -1
- package/dist/shell/assets/app-BxGp504g.js +66 -0
- package/dist/shell/assets/{index-BCqsBT_o.js → index-yvF8R898.js} +1 -1
- package/dist/shell/assets/{maplibre-gl-CxevrnFA.js → maplibre-gl-yKT_5J4l.js} +1 -1
- package/dist/shell/assets/{record-map-Mn5aTqIF.js → record-map-S3uiDn1a.js} +2 -2
- package/dist/shell/assets/{records-map-BdPiGjmg.js → records-map-DVgAeTLw.js} +1 -1
- package/dist/shell/assets/{shared-protobase-ui-CRhV6ipQ.js → shared-protobase-ui-BjAZ-78p.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-CYs0l-vz.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),
|
|
@@ -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";
|
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,10 +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";
|
|
16
|
+
import { SignUpPage } from "./sign-up-page.js";
|
|
14
17
|
import { readStaffLink, withoutStaffLink } from "./staff-link.js";
|
|
15
18
|
import { StaffSignInPage } from "./staff-sign-in-page.js";
|
|
16
19
|
import { TwoFactorPage } from "./two-factor-page.js";
|
|
20
|
+
import { useSignInAddress } from "./use-sign-in-address.js";
|
|
17
21
|
import { readInvitationLink, withoutInvitationLink } from "../organizations/invitation-link.js";
|
|
18
22
|
import { InvitationPage } from "../organizations/invitation-page.js";
|
|
19
23
|
import { useCallback, useEffect, useState } from "react";
|
|
@@ -34,8 +38,7 @@ const firstPage = () => {
|
|
|
34
38
|
const invitation = readInvitationLink(href);
|
|
35
39
|
if (invitation) return {
|
|
36
40
|
kind: "invitation",
|
|
37
|
-
token: invitation.token
|
|
38
|
-
signingIn: false
|
|
41
|
+
token: invitation.token
|
|
39
42
|
};
|
|
40
43
|
const provider = readSignInLink(href);
|
|
41
44
|
if (provider) return {
|
|
@@ -48,33 +51,62 @@ const firstPage = () => {
|
|
|
48
51
|
providerError
|
|
49
52
|
} : { kind: "sign-in" };
|
|
50
53
|
};
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
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
|
+
};
|
|
55
60
|
/**
|
|
56
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
|
|
57
|
-
* browse, and the right page otherwise: the sign-in
|
|
58
|
-
*
|
|
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.
|
|
59
66
|
*/
|
|
60
67
|
const AuthGate = ({ workspace, children }) => {
|
|
61
|
-
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();
|
|
62
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";
|
|
63
84
|
useEffect(() => {
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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;
|
|
69
96
|
useEffect(() => {
|
|
70
|
-
if (
|
|
71
|
-
}, [
|
|
97
|
+
if (!entered && page.kind === "sign-in" && page.providerError && providerErrorLeft) forget(withoutSignInLink);
|
|
98
|
+
}, [
|
|
99
|
+
entered,
|
|
100
|
+
page,
|
|
101
|
+
providerErrorLeft,
|
|
102
|
+
forget
|
|
103
|
+
]);
|
|
72
104
|
const signInFailed = useCallback((error) => setPage({
|
|
73
105
|
kind: "sign-in",
|
|
74
106
|
error
|
|
75
107
|
}), []);
|
|
76
108
|
const leaveResetPage = (next) => {
|
|
77
|
-
|
|
109
|
+
forget(withoutResetLink);
|
|
78
110
|
setPage(next);
|
|
79
111
|
};
|
|
80
112
|
const passwordChanged = async () => {
|
|
@@ -86,7 +118,7 @@ const AuthGate = ({ workspace, children }) => {
|
|
|
86
118
|
};
|
|
87
119
|
const backToSignIn = () => {
|
|
88
120
|
setPage({ kind: "sign-in" });
|
|
89
|
-
signOut().then(
|
|
121
|
+
signOut().then(() => open("sign-in", { replace: true }));
|
|
90
122
|
};
|
|
91
123
|
switch (state.kind) {
|
|
92
124
|
case "loading": return /* @__PURE__ */ jsxs("div", {
|
|
@@ -105,36 +137,33 @@ const AuthGate = ({ workspace, children }) => {
|
|
|
105
137
|
});
|
|
106
138
|
case "needs-admin": return /* @__PURE__ */ jsx(FirstRunPage, { onCheckAgain: recheck });
|
|
107
139
|
}
|
|
140
|
+
if (state.kind === "signed-in" && address) return /* @__PURE__ */ jsx(Redirect, { to: next ?? "/" });
|
|
108
141
|
if (page.kind === "reset-password") return /* @__PURE__ */ jsx(ResetPasswordPage, {
|
|
109
142
|
token: page.token,
|
|
110
143
|
onDone: () => void passwordChanged(),
|
|
111
|
-
onRequestNewLink: passwordReset ? () =>
|
|
144
|
+
onRequestNewLink: passwordReset ? () => {
|
|
145
|
+
setPage({ kind: "sign-in" });
|
|
146
|
+
navigate(signInPath({ step: "forgot-password" }), { replace: true });
|
|
147
|
+
} : void 0,
|
|
112
148
|
onBack: () => leaveResetPage({ kind: "sign-in" })
|
|
113
149
|
});
|
|
114
150
|
if (page.kind === "staff-sign-in") return /* @__PURE__ */ jsx(StaffSignInPage, {
|
|
115
151
|
link: page.link,
|
|
116
152
|
onBack: () => {
|
|
117
|
-
|
|
153
|
+
forget(withoutStaffLink);
|
|
118
154
|
setPage({ kind: "sign-in" });
|
|
119
155
|
}
|
|
120
156
|
});
|
|
121
157
|
const signedOut = state.kind === "signed-out" || state.kind === "visitor";
|
|
122
|
-
if (page.kind === "invitation" && (state.kind === "signed-in" || signedOut && !
|
|
158
|
+
if (page.kind === "invitation" && (state.kind === "signed-in" || signedOut && !address)) return /* @__PURE__ */ jsx(InvitationPage, {
|
|
123
159
|
token: page.token,
|
|
124
160
|
onDone: () => {
|
|
125
|
-
|
|
161
|
+
forget(withoutInvitationLink);
|
|
126
162
|
setPage({ kind: "sign-in" });
|
|
127
163
|
},
|
|
128
|
-
onSignIn: () =>
|
|
129
|
-
setPage({
|
|
130
|
-
...page,
|
|
131
|
-
signingIn: true
|
|
132
|
-
});
|
|
133
|
-
showSignIn();
|
|
134
|
-
}
|
|
164
|
+
onSignIn: () => open()
|
|
135
165
|
});
|
|
136
166
|
if (state.kind === "signed-in") return children;
|
|
137
|
-
if (state.kind === "visitor" && page.kind === "sign-in" && !page.notice && !page.error && !page.providerError) return children;
|
|
138
167
|
if (state.kind === "two-factor") return /* @__PURE__ */ jsx(TwoFactorPage, {
|
|
139
168
|
methods: state.methods,
|
|
140
169
|
onBack: backToSignIn
|
|
@@ -145,24 +174,52 @@ const AuthGate = ({ workspace, children }) => {
|
|
|
145
174
|
workspace,
|
|
146
175
|
onFailed: signInFailed
|
|
147
176
|
});
|
|
148
|
-
if (page.kind === "forgot-password") return /* @__PURE__ */ jsx(ForgotPasswordPage, { onBack: () => setPage({ kind: "sign-in" }) });
|
|
149
|
-
if (page.kind === "email-code") return /* @__PURE__ */ jsx(EmailCodePage, {
|
|
150
|
-
email: page.email,
|
|
151
|
-
onBack: () => setPage({ kind: "sign-in" })
|
|
152
|
-
});
|
|
153
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);
|
|
154
210
|
const providerError = signIn.providerError && providerMessage(signIn.providerError, signInProviders.length === 1 ? signInProviders[0].name : "the provider");
|
|
155
211
|
const error = signIn.error ?? providerError;
|
|
156
212
|
return /* @__PURE__ */ jsx(SignInPage, {
|
|
157
213
|
workspace,
|
|
158
214
|
notice: signIn.notice,
|
|
159
215
|
error,
|
|
160
|
-
onForgotPassword: passwordReset ? () =>
|
|
161
|
-
onCodeSent: (email) =>
|
|
162
|
-
|
|
163
|
-
email
|
|
164
|
-
}
|
|
165
|
-
|
|
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
|
|
166
223
|
}, error);
|
|
167
224
|
};
|
|
168
225
|
//#endregion
|
|
@@ -14,6 +14,7 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
14
14
|
const [state, setState] = useState({ kind: "loading" });
|
|
15
15
|
const [signInMethods, setSignInMethods] = useState(["password"]);
|
|
16
16
|
const [passwordReset, setPasswordReset] = useState(false);
|
|
17
|
+
const [canSignUp, setCanSignUp] = useState(false);
|
|
17
18
|
const [passkeyDomains, setPasskeyDomains] = useState([]);
|
|
18
19
|
const [socialProviders, setSocialProviders] = useState([]);
|
|
19
20
|
const [platformSignIn, setPlatformSignIn] = useState();
|
|
@@ -46,6 +47,7 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
46
47
|
session.status().then(async (status) => {
|
|
47
48
|
setSignInMethods(status.signInMethods);
|
|
48
49
|
setPasswordReset(status.passwordReset);
|
|
50
|
+
setCanSignUp(Boolean(status.signUp));
|
|
49
51
|
setPasskeyDomains(status.passkeyDomains ?? []);
|
|
50
52
|
setSocialProviders(status.socialProviders);
|
|
51
53
|
setPlatformSignIn(status.platformSignIn);
|
|
@@ -66,6 +68,7 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
66
68
|
session,
|
|
67
69
|
signInMethods,
|
|
68
70
|
passwordReset,
|
|
71
|
+
canSignUp,
|
|
69
72
|
passkeyDomains,
|
|
70
73
|
socialProviders,
|
|
71
74
|
signInProviders: socialProviders.map((id) => ({
|
|
@@ -76,11 +79,14 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
76
79
|
...organizations && { organizations },
|
|
77
80
|
markSignedOut,
|
|
78
81
|
recheck,
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
...anonymous && { browse: () => {
|
|
83
|
+
setState((current) => current.kind === "signed-out" ? { kind: "visitor" } : current);
|
|
84
|
+
} },
|
|
81
85
|
signIn: async (email, password) => finish(await session.signIn(email, password)),
|
|
82
86
|
sendSignInCode: (email) => session.sendSignInCode(email),
|
|
83
87
|
signInWithCode: async (email, code) => finish(await session.signInWithCode(email, code)),
|
|
88
|
+
sendSignUpCode: (email) => session.sendSignUpCode(email),
|
|
89
|
+
signUp: async (input) => enter(await session.signUp(input)),
|
|
84
90
|
signInWithPasskey: async (domain) => enter(await session.signInWithPasskey(domain)),
|
|
85
91
|
verifyTwoFactor: async (input) => enter(await session.verifyTwoFactor(input)),
|
|
86
92
|
signInSocial: async (provider) => {
|
|
@@ -102,6 +108,7 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
102
108
|
session,
|
|
103
109
|
signInMethods,
|
|
104
110
|
passwordReset,
|
|
111
|
+
canSignUp,
|
|
105
112
|
passkeyDomains,
|
|
106
113
|
socialProviders,
|
|
107
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 };
|
|
@@ -14,7 +14,7 @@ const Divider = () => /* @__PURE__ */ jsxs("div", {
|
|
|
14
14
|
/* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" })
|
|
15
15
|
]
|
|
16
16
|
});
|
|
17
|
-
const SignInForm = ({ onSubmit, busy, error, workspace, notice, methods = ["password"], onSendCode, onPasskey, passkeyDomains = [], onForgotPassword, providers = [], onContinueWith, onBrowse }) => {
|
|
17
|
+
const SignInForm = ({ onSubmit, busy, error, workspace, notice, methods = ["password"], onSendCode, onPasskey, passkeyDomains = [], onForgotPassword, providers = [], onContinueWith, onSignUp, onBrowse }) => {
|
|
18
18
|
const [email, setEmail] = useState("");
|
|
19
19
|
const [password, setPassword] = useState("");
|
|
20
20
|
const emailInput = useRef(null);
|
|
@@ -133,6 +133,19 @@ const SignInForm = ({ onSubmit, busy, error, workspace, notice, methods = ["pass
|
|
|
133
133
|
})
|
|
134
134
|
]
|
|
135
135
|
}),
|
|
136
|
+
onSignUp && /* @__PURE__ */ jsxs("p", {
|
|
137
|
+
className: "mt-4 text-center text-[13px] text-muted-foreground",
|
|
138
|
+
children: [
|
|
139
|
+
"New here?",
|
|
140
|
+
" ",
|
|
141
|
+
/* @__PURE__ */ jsx("button", {
|
|
142
|
+
type: "button",
|
|
143
|
+
onClick: onSignUp,
|
|
144
|
+
className: "font-medium text-primary-text hover:underline",
|
|
145
|
+
children: "Create an account"
|
|
146
|
+
})
|
|
147
|
+
]
|
|
148
|
+
}),
|
|
136
149
|
onBrowse && /* @__PURE__ */ jsx("button", {
|
|
137
150
|
type: "button",
|
|
138
151
|
onClick: onBrowse,
|
|
@@ -5,7 +5,7 @@ import { useState } from "react";
|
|
|
5
5
|
import { AuthError } from "@protobase/client";
|
|
6
6
|
import { jsx } from "react/jsx-runtime";
|
|
7
7
|
//#region src/auth/sign-in-page.tsx
|
|
8
|
-
const SignInPage = ({ workspace, notice, onForgotPassword, onBrowse, onCodeSent, error: initialError }) => {
|
|
8
|
+
const SignInPage = ({ workspace, notice, onForgotPassword, onSignUp, onBrowse, onCodeSent, error: initialError }) => {
|
|
9
9
|
const { signIn, signInSocial, signInProviders, signInMethods, passkeyDomains, sendSignInCode, signInWithPasskey } = useAuth();
|
|
10
10
|
const [busy, setBusy] = useState(false);
|
|
11
11
|
const [error, setError] = useState(initialError);
|
|
@@ -27,6 +27,7 @@ const SignInPage = ({ workspace, notice, onForgotPassword, onBrowse, onCodeSent,
|
|
|
27
27
|
notice,
|
|
28
28
|
methods: signInMethods,
|
|
29
29
|
onForgotPassword,
|
|
30
|
+
onSignUp,
|
|
30
31
|
onBrowse,
|
|
31
32
|
providers: signInProviders,
|
|
32
33
|
onContinueWith: (provider) => void attempt(() => signInSocial(provider), signInMessage),
|
|
@@ -15,7 +15,8 @@ export type SignInPromptProps = {
|
|
|
15
15
|
});
|
|
16
16
|
/**
|
|
17
17
|
* What a visitor who is not signed in is told about something they could use once signed in: the prompt's title, what
|
|
18
|
-
* signing in gives and its benefits, with "Sign in" and
|
|
18
|
+
* signing in gives and its benefits, with "Sign in" and a way to make an account: the page the prompt names, or the
|
|
19
|
+
* app's own sign-up when the server has it. As a page
|
|
19
20
|
* it stands in for a list the visitor cannot see; as a dialog it answers a press on a button that needs an account.
|
|
20
21
|
*/
|
|
21
22
|
export declare const SignInPrompt: (props: SignInPromptProps) => import("react").JSX.Element;
|
|
@@ -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,22 +18,27 @@ const Benefits = ({ benefits }) => benefits && benefits.length > 0 ? /* @__PURE_
|
|
|
17
18
|
}, benefit))
|
|
18
19
|
}) : null;
|
|
19
20
|
const Actions = ({ prompt }) => {
|
|
20
|
-
const {
|
|
21
|
-
|
|
21
|
+
const { canSignUp } = useAuth();
|
|
22
|
+
const { open } = useSignInAddress();
|
|
23
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [prompt.signUp ? /* @__PURE__ */ jsx("a", {
|
|
22
24
|
href: prompt.signUp.href,
|
|
23
25
|
target: "_blank",
|
|
24
26
|
rel: "noreferrer",
|
|
25
27
|
className: buttonClass(),
|
|
26
28
|
children: prompt.signUp.label
|
|
29
|
+
}) : canSignUp && /* @__PURE__ */ jsx(Button, {
|
|
30
|
+
onClick: () => open("sign-up"),
|
|
31
|
+
children: "Create an account"
|
|
27
32
|
}), /* @__PURE__ */ jsx(Button, {
|
|
28
33
|
variant: "primary",
|
|
29
|
-
onClick:
|
|
34
|
+
onClick: () => open(),
|
|
30
35
|
children: "Sign in"
|
|
31
36
|
})] });
|
|
32
37
|
};
|
|
33
38
|
/**
|
|
34
39
|
* What a visitor who is not signed in is told about something they could use once signed in: the prompt's title, what
|
|
35
|
-
* signing in gives and its benefits, with "Sign in" and
|
|
40
|
+
* signing in gives and its benefits, with "Sign in" and a way to make an account: the page the prompt names, or the
|
|
41
|
+
* app's own sign-up when the server has it. As a page
|
|
36
42
|
* it stands in for a list the visitor cannot see; as a dialog it answers a press on a button that needs an account.
|
|
37
43
|
*/
|
|
38
44
|
const SignInPrompt = (props) => {
|