@protobase/ui 0.15.4 → 0.16.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/account/two-factor-setup.js +6 -5
- package/dist/app/app.js +2 -1
- package/dist/app/create/create-page.js +1 -0
- package/dist/app/create/create-record-form.js +2 -1
- package/dist/app/displays/custom-display.js +1 -1
- package/dist/app/displays/map-display.js +50 -34
- package/dist/app/displays/map-groups.js +12 -3
- package/dist/app/displays/measured-heights.js +23 -0
- package/dist/app/displays/record-card.js +2 -2
- package/dist/app/displays/virtual-cards.js +12 -14
- package/dist/app/field-editor.js +9 -1
- package/dist/app/index.html +2 -2
- package/dist/app/list-page.js +2 -2
- package/dist/app/map/approximate-position.js +76 -0
- package/dist/app/map/area-features.js +80 -0
- package/dist/app/map/maplibre.js +11 -0
- package/dist/app/map/position-areas.js +78 -0
- package/dist/app/map/record-map.js +2 -7
- package/dist/app/map/record-position.js +5 -4
- package/dist/app/map/records-map.js +24 -7
- package/dist/app/map/theme-color.js +23 -0
- package/dist/app/meta-gate.js +1 -1
- package/dist/app/nav-recent.js +1 -1
- package/dist/app/pages/blocks/card-frame.js +2 -2
- package/dist/app/pages/blocks/grid.js +10 -2
- package/dist/app/pages/blocks/link.js +1 -1
- package/dist/app/pages/blocks/page-block.js +1 -1
- package/dist/app/pages/blocks/stat.js +9 -5
- package/dist/app/pages/field-value.js +1 -1
- package/dist/app/record-page.js +8 -4
- package/dist/app/router.js +7 -8
- package/dist/app/saved/saved-records.js +1 -1
- package/dist/app/search/search-model.js +1 -1
- package/dist/app/searches/use-save-search.js +4 -3
- package/dist/app/source-notice.js +2 -2
- package/dist/app/use-record-editor.js +5 -3
- package/dist/app-shell/app-shell.d.ts +1 -0
- package/dist/app-shell/app-shell.js +6 -4
- package/dist/app-shell/page-scroll.js +21 -4
- package/dist/app-shell/sidebar.d.ts +1 -0
- package/dist/app-shell/sidebar.js +4 -4
- package/dist/app-shell/top-bar.js +4 -4
- package/dist/auth/auth-gate.js +18 -9
- package/dist/auth/auth-provider.js +1 -0
- package/dist/auth/code-form.js +77 -0
- package/dist/auth/email-code-form.js +18 -77
- package/dist/auth/forgot-password-form.js +10 -10
- package/dist/auth/forgot-password-page.js +2 -2
- package/dist/auth/resend-code-button.js +29 -0
- package/dist/auth/sign-in-form.js +7 -8
- package/dist/auth/sign-in-page.js +3 -3
- package/dist/auth/sign-in-prompt.js +13 -9
- package/dist/auth/sign-up-code-form.js +22 -0
- package/dist/auth/sign-up-details-form.js +81 -111
- package/dist/auth/sign-up-page.js +17 -8
- package/dist/auth/two-factor-form.js +3 -3
- package/dist/auth/use-sign-in-address.js +21 -8
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/lib/flip-guard.js +43 -0
- package/dist/lib/page-scroll-lock.js +20 -0
- package/dist/lib/use-focus-trap.js +4 -4
- package/dist/organizations/invitation-page.js +7 -6
- package/dist/package.js +1 -1
- package/dist/primitives/otp-input.d.ts +21 -0
- package/dist/primitives/otp-input.js +117 -0
- package/dist/primitives/otp-value.js +23 -0
- package/dist/primitives/route-link.d.ts +1 -0
- package/dist/primitives/route-link.js +20 -0
- package/dist/primitives/textarea.js +36 -0
- package/dist/record-view/field.js +56 -53
- package/dist/shell/assets/app-DJAZP1yB.js +66 -0
- package/dist/shell/assets/index-C5pYkUfv.css +2 -0
- package/dist/shell/assets/{index-CrtAqGCx.js → index-LBhMBiK4.js} +1 -1
- package/dist/shell/assets/maplibre-Dy0ZTnFZ.js +814 -0
- package/dist/shell/assets/record-map-BiWSbqqT.js +1 -0
- package/dist/shell/assets/records-map-DabYc3ad.js +1 -0
- package/dist/shell/assets/shared-protobase-ui-BURelnPG.js +1 -0
- package/dist/shell/index.html +6 -6
- package/dist/shell/protobase.shell.json +1 -1
- package/dist/styles.css +15 -0
- package/dist/toasts/toast.js +22 -6
- package/package.json +4 -4
- package/dist/auth/code-input.js +0 -17
- package/dist/auth/sign-in-address.js +0 -47
- package/dist/shell/assets/app-CpFoFNtG.js +0 -66
- package/dist/shell/assets/index-ibaHR5wQ.css +0 -2
- package/dist/shell/assets/maplibre-gl-B_TZ9n3D.js +0 -1
- package/dist/shell/assets/record-map-B2mkMEcE.js +0 -814
- package/dist/shell/assets/records-map-CAj21Al1.js +0 -1
- package/dist/shell/assets/shared-protobase-ui-D942ARkw.js +0 -1
- /package/dist/shell/assets/{record-map-DBE96hFo.css → maplibre-DBE96hFo.css} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { RouteLink } from "../primitives/route-link.js";
|
|
2
3
|
import { Logo } from "./logo.js";
|
|
3
4
|
import { navGroups } from "./nav.js";
|
|
4
5
|
import { ThemeSwitch } from "./theme-switch.js";
|
|
@@ -9,7 +10,7 @@ import { SidebarGroup } from "./sidebar-group.js";
|
|
|
9
10
|
import { LogIn } from "lucide-react";
|
|
10
11
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
12
|
//#region src/app-shell/sidebar.tsx
|
|
12
|
-
const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenuOpen, groups = navGroups, userMenu, onNavigate, onSignOut,
|
|
13
|
+
const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenuOpen, groups = navGroups, userMenu, onNavigate, onSignOut, signIn, organizations, roles, theme, sticky }) => {
|
|
13
14
|
const expanding = mode === "icon-expand";
|
|
14
15
|
const expanded = expanding && forceExpanded;
|
|
15
16
|
const inline = isTextMode(mode) || expanding;
|
|
@@ -62,9 +63,8 @@ const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenu
|
|
|
62
63
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [theme && inline && /* @__PURE__ */ jsx(ThemeSwitch, {
|
|
63
64
|
theme,
|
|
64
65
|
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
|
-
}),
|
|
66
|
-
|
|
67
|
-
onClick: onSignIn,
|
|
66
|
+
}), signIn && /* @__PURE__ */ jsxs(RouteLink, {
|
|
67
|
+
to: signIn,
|
|
68
68
|
"aria-label": "Sign in",
|
|
69
69
|
className: "flex min-h-11 w-full items-center gap-2.5 rounded-md p-1.5 text-left text-[13px] font-medium text-muted-foreground hover:bg-muted hover:text-foreground md:min-h-0",
|
|
70
70
|
children: [/* @__PURE__ */ jsx(LogIn, { className: "size-4 shrink-0" }), inline && /* @__PURE__ */ jsx("span", {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
|
-
import { backTarget } from "./back-target.js";
|
|
3
2
|
import { Breadcrumb, inApp } from "./breadcrumb.js";
|
|
3
|
+
import { backTarget } from "./back-target.js";
|
|
4
4
|
import { GlobalSearch } from "./global-search.js";
|
|
5
5
|
import { useEffect, useRef, useState } from "react";
|
|
6
6
|
import { ChevronLeft, Menu, Search, X } from "lucide-react";
|
|
@@ -25,7 +25,7 @@ const TopBar = ({ breadcrumb, onNavigate, actions, onMenu, menuOpen, menuControl
|
|
|
25
25
|
}, [search]);
|
|
26
26
|
return /* @__PURE__ */ jsxs("header", {
|
|
27
27
|
ref,
|
|
28
|
-
className: cn(sticky ? "sticky top-0 z-30" : "relative", "flex h-12 shrink-0 items-center gap-1 border-b bg-background
|
|
28
|
+
className: cn(sticky ? "sticky top-0 z-30" : "relative", "flex h-12 shrink-0 items-center gap-1 border-b bg-background pl-4 pr-2 md:gap-4 md:px-5"),
|
|
29
29
|
children: [
|
|
30
30
|
onMenu && /* @__PURE__ */ jsx("button", {
|
|
31
31
|
type: "button",
|
|
@@ -33,7 +33,7 @@ const TopBar = ({ breadcrumb, onNavigate, actions, onMenu, menuOpen, menuControl
|
|
|
33
33
|
"aria-expanded": Boolean(menuOpen),
|
|
34
34
|
"aria-controls": menuControls,
|
|
35
35
|
onClick: onMenu,
|
|
36
|
-
className: cn(iconButton, "-ml-
|
|
36
|
+
className: cn(iconButton, "-ml-3"),
|
|
37
37
|
children: /* @__PURE__ */ jsx(Menu, { className: "size-5" })
|
|
38
38
|
}),
|
|
39
39
|
back && /* @__PURE__ */ jsx("a", {
|
|
@@ -44,7 +44,7 @@ const TopBar = ({ breadcrumb, onNavigate, actions, onMenu, menuOpen, menuControl
|
|
|
44
44
|
event.preventDefault();
|
|
45
45
|
onNavigate(back.href);
|
|
46
46
|
},
|
|
47
|
-
className: cn(iconButton, !onMenu && "-ml-
|
|
47
|
+
className: cn(iconButton, !onMenu && "-ml-3"),
|
|
48
48
|
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-5" })
|
|
49
49
|
}),
|
|
50
50
|
/* @__PURE__ */ jsx(Breadcrumb, {
|
package/dist/auth/auth-gate.js
CHANGED
|
@@ -11,7 +11,6 @@ import { ForgotPasswordPage } from "./forgot-password-page.js";
|
|
|
11
11
|
import { ProviderSignInPage } from "./provider-sign-in-page.js";
|
|
12
12
|
import { ResetPasswordPage } from "./reset-password-page.js";
|
|
13
13
|
import { SetupRequiredPage } from "./setup-required-page.js";
|
|
14
|
-
import { signInPath } from "./sign-in-address.js";
|
|
15
14
|
import { SignInPage } from "./sign-in-page.js";
|
|
16
15
|
import { SignUpPage } from "./sign-up-page.js";
|
|
17
16
|
import { readStaffLink, withoutStaffLink } from "./staff-link.js";
|
|
@@ -22,6 +21,7 @@ import { readInvitationLink, withoutInvitationLink } from "../organizations/invi
|
|
|
22
21
|
import { InvitationPage } from "../organizations/invitation-page.js";
|
|
23
22
|
import { useCallback, useEffect, useState } from "react";
|
|
24
23
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
24
|
+
import { signInPath } from "@protobase/schema";
|
|
25
25
|
//#region src/auth/auth-gate.tsx
|
|
26
26
|
const firstPage = () => {
|
|
27
27
|
const href = window.location.href;
|
|
@@ -67,10 +67,11 @@ const Redirect = ({ to }) => {
|
|
|
67
67
|
const AuthGate = ({ workspace, children }) => {
|
|
68
68
|
const { state, recheck, passwordReset, signOut, signInProviders, canSignUp, browse } = useAuth();
|
|
69
69
|
const { path, params, basePath, navigate } = useRouter();
|
|
70
|
-
const { address, next, open, leave } = useSignInAddress();
|
|
70
|
+
const { address, next, open, link, leave } = useSignInAddress();
|
|
71
71
|
const [page, setPage] = useState(firstPage);
|
|
72
72
|
const [codeEmail, setCodeEmail] = useState();
|
|
73
73
|
const [signUpEmail, setSignUpEmail] = useState();
|
|
74
|
+
const [signUpTicket, setSignUpTicket] = useState();
|
|
74
75
|
const href = new URL(`${basePath}${path}${params.size > 0 ? `?${params}` : ""}`, window.location.origin).href;
|
|
75
76
|
const forget = useCallback((without) => {
|
|
76
77
|
const below = without(href).slice(basePath.length);
|
|
@@ -87,6 +88,7 @@ const AuthGate = ({ workspace, children }) => {
|
|
|
87
88
|
if (page.kind === "provider-sign-in" || page.kind === "sign-in" && (page.notice || page.error || page.providerError)) setPage({ kind: "sign-in" });
|
|
88
89
|
setCodeEmail(void 0);
|
|
89
90
|
setSignUpEmail(void 0);
|
|
91
|
+
setSignUpTicket(void 0);
|
|
90
92
|
}, [
|
|
91
93
|
entered,
|
|
92
94
|
page,
|
|
@@ -161,7 +163,7 @@ const AuthGate = ({ workspace, children }) => {
|
|
|
161
163
|
forget(withoutInvitationLink);
|
|
162
164
|
setPage({ kind: "sign-in" });
|
|
163
165
|
},
|
|
164
|
-
|
|
166
|
+
signIn: link()
|
|
165
167
|
});
|
|
166
168
|
if (state.kind === "signed-in") return children;
|
|
167
169
|
if (state.kind === "two-factor") return /* @__PURE__ */ jsx(TwoFactorPage, {
|
|
@@ -184,25 +186,32 @@ const AuthGate = ({ workspace, children }) => {
|
|
|
184
186
|
"forgot-password": passwordReset,
|
|
185
187
|
"email-code": codeEmail !== void 0,
|
|
186
188
|
"sign-up": canSignUp,
|
|
187
|
-
"sign-up-code": canSignUp && signUpEmail !== void 0
|
|
189
|
+
"sign-up-code": canSignUp && signUpEmail !== void 0,
|
|
190
|
+
"sign-up-details": canSignUp && signUpTicket !== void 0
|
|
188
191
|
}[address.step] ? address.step : "sign-in";
|
|
189
192
|
const backTo = (target) => () => open(target, { replace: true });
|
|
190
193
|
const onBrowse = browse && (() => {
|
|
191
194
|
browse();
|
|
192
195
|
leave();
|
|
193
196
|
});
|
|
194
|
-
if (step === "forgot-password") return /* @__PURE__ */ jsx(ForgotPasswordPage, {
|
|
197
|
+
if (step === "forgot-password") return /* @__PURE__ */ jsx(ForgotPasswordPage, { back: link("sign-in", { replace: true }) });
|
|
195
198
|
if (step === "email-code") return /* @__PURE__ */ jsx(EmailCodePage, {
|
|
196
199
|
email: codeEmail,
|
|
197
200
|
onBack: backTo("sign-in")
|
|
198
201
|
});
|
|
199
|
-
if (step === "sign-up" || step === "sign-up-code") return /* @__PURE__ */ jsx(SignUpPage, {
|
|
202
|
+
if (step === "sign-up" || step === "sign-up-code" || step === "sign-up-details") return /* @__PURE__ */ jsx(SignUpPage, {
|
|
200
203
|
workspace,
|
|
201
|
-
email: step === "sign-up
|
|
204
|
+
email: step === "sign-up" ? void 0 : signUpEmail,
|
|
205
|
+
ticket: step === "sign-up-details" ? signUpTicket : void 0,
|
|
202
206
|
onCodeSent: (email) => {
|
|
203
207
|
setSignUpEmail(email);
|
|
208
|
+
setSignUpTicket(void 0);
|
|
204
209
|
open("sign-up-code");
|
|
205
210
|
},
|
|
211
|
+
onVerified: (ticket) => {
|
|
212
|
+
setSignUpTicket(ticket);
|
|
213
|
+
open("sign-up-details", { replace: true });
|
|
214
|
+
},
|
|
206
215
|
onBack: backTo("sign-up"),
|
|
207
216
|
onSignIn: () => open("sign-in"),
|
|
208
217
|
onBrowse
|
|
@@ -213,12 +222,12 @@ const AuthGate = ({ workspace, children }) => {
|
|
|
213
222
|
workspace,
|
|
214
223
|
notice: signIn.notice,
|
|
215
224
|
error,
|
|
216
|
-
|
|
225
|
+
forgotPassword: passwordReset ? link("forgot-password") : void 0,
|
|
217
226
|
onCodeSent: (email) => {
|
|
218
227
|
setCodeEmail(email);
|
|
219
228
|
open("email-code");
|
|
220
229
|
},
|
|
221
|
-
|
|
230
|
+
signUp: canSignUp ? link("sign-up") : void 0,
|
|
222
231
|
onBrowse
|
|
223
232
|
}, error);
|
|
224
233
|
};
|
|
@@ -86,6 +86,7 @@ const AuthProvider = ({ session: given, children }) => {
|
|
|
86
86
|
sendSignInCode: (email) => session.sendSignInCode(email),
|
|
87
87
|
signInWithCode: async (email, code) => finish(await session.signInWithCode(email, code)),
|
|
88
88
|
sendSignUpCode: (email) => session.sendSignUpCode(email),
|
|
89
|
+
verifySignUpCode: (email, code) => session.verifySignUpCode(email, code),
|
|
89
90
|
signUp: async (input) => enter(await session.signUp(input)),
|
|
90
91
|
signInWithPasskey: async (domain) => enter(await session.signInWithPasskey(domain)),
|
|
91
92
|
verifyTwoFactor: async (input) => enter(await session.verifyTwoFactor(input)),
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Button } from "../primitives/button.js";
|
|
2
|
+
import { OtpInput } from "../primitives/otp-input.js";
|
|
3
|
+
import { AuthLayout } from "./auth-layout.js";
|
|
4
|
+
import { ResendCodeButton } from "./resend-code-button.js";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/auth/code-form.tsx
|
|
8
|
+
/**
|
|
9
|
+
* A step that checks an emailed code on its own: where it went, a box per digit, a new code after a wait, and a way back
|
|
10
|
+
* to another address. Nothing else is asked for until the code is right.
|
|
11
|
+
*/
|
|
12
|
+
const CodeForm = ({ title, description, explanation, submitLabel, onSubmit, onResend, onChangeAddress, changeAddressLabel, busy, error, notice, resendCooldown }) => {
|
|
13
|
+
const [code, setCode] = useState("");
|
|
14
|
+
return /* @__PURE__ */ jsxs(AuthLayout, {
|
|
15
|
+
title,
|
|
16
|
+
description,
|
|
17
|
+
children: [
|
|
18
|
+
/* @__PURE__ */ jsx("p", {
|
|
19
|
+
className: "mb-4 text-[13px] text-muted-foreground",
|
|
20
|
+
children: explanation
|
|
21
|
+
}),
|
|
22
|
+
notice && /* @__PURE__ */ jsx("p", {
|
|
23
|
+
role: "status",
|
|
24
|
+
className: "mb-4 rounded-md bg-success-soft px-3 py-2 text-xs font-medium text-success-text",
|
|
25
|
+
children: notice
|
|
26
|
+
}),
|
|
27
|
+
/* @__PURE__ */ jsxs("form", {
|
|
28
|
+
className: "flex flex-col gap-4",
|
|
29
|
+
onSubmit: (event) => {
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
onSubmit(code);
|
|
32
|
+
},
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ jsxs("label", {
|
|
35
|
+
className: "flex flex-col gap-1.5 text-[13px] font-medium",
|
|
36
|
+
children: ["Code", /* @__PURE__ */ jsx(OtpInput, {
|
|
37
|
+
name: "code",
|
|
38
|
+
required: true,
|
|
39
|
+
autoFocus: true,
|
|
40
|
+
value: code,
|
|
41
|
+
invalid: Boolean(error),
|
|
42
|
+
onChange: setCode,
|
|
43
|
+
onComplete: (complete) => !busy && onSubmit(complete)
|
|
44
|
+
})]
|
|
45
|
+
}),
|
|
46
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
47
|
+
role: "alert",
|
|
48
|
+
className: "text-xs font-medium text-danger-text",
|
|
49
|
+
children: error
|
|
50
|
+
}),
|
|
51
|
+
/* @__PURE__ */ jsx(Button, {
|
|
52
|
+
variant: "primary",
|
|
53
|
+
type: "submit",
|
|
54
|
+
loading: busy,
|
|
55
|
+
className: "min-h-10",
|
|
56
|
+
children: submitLabel
|
|
57
|
+
}),
|
|
58
|
+
/* @__PURE__ */ jsxs("div", {
|
|
59
|
+
className: "flex flex-wrap justify-center gap-x-2 sm:justify-between",
|
|
60
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
61
|
+
variant: "ghost",
|
|
62
|
+
onClick: onChangeAddress,
|
|
63
|
+
className: "min-h-10",
|
|
64
|
+
children: changeAddressLabel
|
|
65
|
+
}), /* @__PURE__ */ jsx(ResendCodeButton, {
|
|
66
|
+
onResend,
|
|
67
|
+
disabled: busy,
|
|
68
|
+
cooldown: resendCooldown
|
|
69
|
+
})]
|
|
70
|
+
})
|
|
71
|
+
]
|
|
72
|
+
})
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
//#endregion
|
|
77
|
+
export { CodeForm };
|
|
@@ -1,81 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { AuthLayout } from "./auth-layout.js";
|
|
4
|
-
import { useState } from "react";
|
|
5
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { CodeForm } from "./code-form.js";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
3
|
//#region src/auth/email-code-form.tsx
|
|
7
4
|
/** The second half of signing in with an emailed code: the code from the email. */
|
|
8
|
-
const EmailCodeForm = ({ email,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}),
|
|
25
|
-
notice && /* @__PURE__ */ jsx("p", {
|
|
26
|
-
role: "status",
|
|
27
|
-
className: "mb-4 rounded-md bg-success-soft px-3 py-2 text-xs font-medium text-success-text",
|
|
28
|
-
children: notice
|
|
29
|
-
}),
|
|
30
|
-
/* @__PURE__ */ jsxs("form", {
|
|
31
|
-
className: "flex flex-col gap-4",
|
|
32
|
-
onSubmit: (event) => {
|
|
33
|
-
event.preventDefault();
|
|
34
|
-
onSubmit(code.trim());
|
|
35
|
-
},
|
|
36
|
-
children: [
|
|
37
|
-
/* @__PURE__ */ jsxs("label", {
|
|
38
|
-
className: "flex flex-col gap-1.5 text-[13px] font-medium",
|
|
39
|
-
children: ["Code", /* @__PURE__ */ jsx(CodeInput, {
|
|
40
|
-
name: "code",
|
|
41
|
-
required: true,
|
|
42
|
-
autoFocus: true,
|
|
43
|
-
value: code,
|
|
44
|
-
invalid: Boolean(error),
|
|
45
|
-
onChange: (event) => setCode(event.target.value)
|
|
46
|
-
})]
|
|
47
|
-
}),
|
|
48
|
-
error && /* @__PURE__ */ jsx("p", {
|
|
49
|
-
role: "alert",
|
|
50
|
-
className: "text-xs font-medium text-danger-text",
|
|
51
|
-
children: error
|
|
52
|
-
}),
|
|
53
|
-
/* @__PURE__ */ jsx(Button, {
|
|
54
|
-
variant: "primary",
|
|
55
|
-
type: "submit",
|
|
56
|
-
loading: busy,
|
|
57
|
-
className: "min-h-10",
|
|
58
|
-
children: "Sign in"
|
|
59
|
-
}),
|
|
60
|
-
/* @__PURE__ */ jsxs("div", {
|
|
61
|
-
className: "flex justify-between gap-2",
|
|
62
|
-
children: [/* @__PURE__ */ jsx(Button, {
|
|
63
|
-
variant: "ghost",
|
|
64
|
-
onClick: onBack,
|
|
65
|
-
className: "min-h-10",
|
|
66
|
-
children: "Back to sign in"
|
|
67
|
-
}), /* @__PURE__ */ jsx(Button, {
|
|
68
|
-
variant: "ghost",
|
|
69
|
-
onClick: onResend,
|
|
70
|
-
disabled: busy,
|
|
71
|
-
className: "min-h-10",
|
|
72
|
-
children: "Send a new code"
|
|
73
|
-
})]
|
|
74
|
-
})
|
|
75
|
-
]
|
|
76
|
-
})
|
|
77
|
-
]
|
|
78
|
-
});
|
|
79
|
-
};
|
|
5
|
+
const EmailCodeForm = ({ email, onBack, ...rest }) => /* @__PURE__ */ jsx(CodeForm, {
|
|
6
|
+
title: "Check your email",
|
|
7
|
+
description: "Enter the code we sent to sign in.",
|
|
8
|
+
explanation: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9
|
+
"If ",
|
|
10
|
+
/* @__PURE__ */ jsx("span", {
|
|
11
|
+
className: "font-medium text-foreground",
|
|
12
|
+
children: email
|
|
13
|
+
}),
|
|
14
|
+
" has an account, a 6-digit code is on its way. It works for 5 minutes, once."
|
|
15
|
+
] }),
|
|
16
|
+
submitLabel: "Sign in",
|
|
17
|
+
onChangeAddress: onBack,
|
|
18
|
+
changeAddressLabel: "Back to sign in",
|
|
19
|
+
...rest
|
|
20
|
+
});
|
|
80
21
|
//#endregion
|
|
81
22
|
export { EmailCodeForm };
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { RouteLink } from "../primitives/route-link.js";
|
|
1
3
|
import { Input } from "../primitives/input.js";
|
|
2
|
-
import { Button } from "../primitives/button.js";
|
|
4
|
+
import { Button, buttonClass } from "../primitives/button.js";
|
|
3
5
|
import { AuthLayout } from "./auth-layout.js";
|
|
4
6
|
import { useState } from "react";
|
|
5
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
8
|
//#region src/auth/forgot-password-form.tsx
|
|
7
|
-
const ForgotPasswordForm = ({ onSubmit,
|
|
9
|
+
const ForgotPasswordForm = ({ onSubmit, back, busy, error, sentTo }) => {
|
|
8
10
|
const [email, setEmail] = useState("");
|
|
9
11
|
if (sentTo) return /* @__PURE__ */ jsxs(AuthLayout, {
|
|
10
12
|
title: "Check your email",
|
|
@@ -18,10 +20,9 @@ const ForgotPasswordForm = ({ onSubmit, onBack, busy, error, sentTo }) => {
|
|
|
18
20
|
}),
|
|
19
21
|
" has an account, an email with a link to choose a new password is on its way. The link works once, for a limited time."
|
|
20
22
|
]
|
|
21
|
-
}), /* @__PURE__ */ jsx(
|
|
22
|
-
|
|
23
|
-
className: "mt-4 min-h-10 w-full",
|
|
24
|
-
onClick: onBack,
|
|
23
|
+
}), /* @__PURE__ */ jsx(RouteLink, {
|
|
24
|
+
to: back,
|
|
25
|
+
className: cn(buttonClass({ variant: "primary" }), "mt-4 min-h-10 w-full"),
|
|
25
26
|
children: "Back to sign in"
|
|
26
27
|
})]
|
|
27
28
|
});
|
|
@@ -61,10 +62,9 @@ const ForgotPasswordForm = ({ onSubmit, onBack, busy, error, sentTo }) => {
|
|
|
61
62
|
className: "min-h-10",
|
|
62
63
|
children: "Send reset link"
|
|
63
64
|
}),
|
|
64
|
-
/* @__PURE__ */ jsx(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
className: "min-h-10",
|
|
65
|
+
/* @__PURE__ */ jsx(RouteLink, {
|
|
66
|
+
to: back,
|
|
67
|
+
className: cn(buttonClass({ variant: "ghost" }), "min-h-10"),
|
|
68
68
|
children: "Back to sign in"
|
|
69
69
|
})
|
|
70
70
|
]
|
|
@@ -6,7 +6,7 @@ import { AuthError } from "@protobase/client";
|
|
|
6
6
|
import { jsx } from "react/jsx-runtime";
|
|
7
7
|
//#region src/auth/forgot-password-page.tsx
|
|
8
8
|
/** Asks the server to email a reset link that leads back to this page. */
|
|
9
|
-
const ForgotPasswordPage = ({
|
|
9
|
+
const ForgotPasswordPage = ({ back }) => {
|
|
10
10
|
const { session } = useAuth();
|
|
11
11
|
const [busy, setBusy] = useState(false);
|
|
12
12
|
const [error, setError] = useState();
|
|
@@ -27,7 +27,7 @@ const ForgotPasswordPage = ({ onBack }) => {
|
|
|
27
27
|
busy,
|
|
28
28
|
error,
|
|
29
29
|
sentTo,
|
|
30
|
-
|
|
30
|
+
back,
|
|
31
31
|
onSubmit: (email) => void submit(email)
|
|
32
32
|
});
|
|
33
33
|
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Button } from "../primitives/button.js";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/auth/resend-code-button.tsx
|
|
5
|
+
/** "Send a new code", counting down first: the screen opens right after a code went out, and so does every resend. */
|
|
6
|
+
const ResendCodeButton = ({ onResend, cooldown = 30, disabled }) => {
|
|
7
|
+
const [until, setUntil] = useState(() => Date.now() + cooldown * 1e3);
|
|
8
|
+
const [now, setNow] = useState(() => Date.now());
|
|
9
|
+
const left = Math.max(0, Math.ceil((until - now) / 1e3));
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (left === 0) return;
|
|
12
|
+
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
13
|
+
return () => clearInterval(timer);
|
|
14
|
+
}, [left]);
|
|
15
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
16
|
+
variant: "ghost",
|
|
17
|
+
disabled: disabled || left > 0,
|
|
18
|
+
onClick: () => {
|
|
19
|
+
const sent = Date.now();
|
|
20
|
+
setUntil(sent + cooldown * 1e3);
|
|
21
|
+
setNow(sent);
|
|
22
|
+
onResend();
|
|
23
|
+
},
|
|
24
|
+
className: "min-h-10",
|
|
25
|
+
children: left > 0 ? `Send a new code in ${left}s` : "Send a new code"
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { ResendCodeButton };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RouteLink } from "../primitives/route-link.js";
|
|
1
2
|
import { Input } from "../primitives/input.js";
|
|
2
3
|
import { Button } from "../primitives/button.js";
|
|
3
4
|
import { AuthLayout } from "./auth-layout.js";
|
|
@@ -14,7 +15,7 @@ const Divider = () => /* @__PURE__ */ jsxs("div", {
|
|
|
14
15
|
/* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" })
|
|
15
16
|
]
|
|
16
17
|
});
|
|
17
|
-
const SignInForm = ({ onSubmit, busy, error, workspace, notice, methods = ["password"], onSendCode, onPasskey, passkeyDomains = [],
|
|
18
|
+
const SignInForm = ({ onSubmit, busy, error, workspace, notice, methods = ["password"], onSendCode, onPasskey, passkeyDomains = [], forgotPassword, providers = [], onContinueWith, signUp, onBrowse }) => {
|
|
18
19
|
const [email, setEmail] = useState("");
|
|
19
20
|
const [password, setPassword] = useState("");
|
|
20
21
|
const emailInput = useRef(null);
|
|
@@ -125,22 +126,20 @@ const SignInForm = ({ onSubmit, busy, error, workspace, notice, methods = ["pass
|
|
|
125
126
|
className: "min-h-10",
|
|
126
127
|
children: [/* @__PURE__ */ jsx(Mail, { className: "size-4" }), "Email me a sign-in code"]
|
|
127
128
|
}),
|
|
128
|
-
withPassword &&
|
|
129
|
-
|
|
130
|
-
onClick: onForgotPassword,
|
|
129
|
+
withPassword && forgotPassword && /* @__PURE__ */ jsx(RouteLink, {
|
|
130
|
+
to: forgotPassword,
|
|
131
131
|
className: "self-center text-[13px] font-medium text-primary-text hover:underline",
|
|
132
132
|
children: "Forgot password?"
|
|
133
133
|
})
|
|
134
134
|
]
|
|
135
135
|
}),
|
|
136
|
-
|
|
136
|
+
signUp && /* @__PURE__ */ jsxs("p", {
|
|
137
137
|
className: "mt-4 text-center text-[13px] text-muted-foreground",
|
|
138
138
|
children: [
|
|
139
139
|
"New here?",
|
|
140
140
|
" ",
|
|
141
|
-
/* @__PURE__ */ jsx(
|
|
142
|
-
|
|
143
|
-
onClick: onSignUp,
|
|
141
|
+
/* @__PURE__ */ jsx(RouteLink, {
|
|
142
|
+
to: signUp,
|
|
144
143
|
className: "font-medium text-primary-text hover:underline",
|
|
145
144
|
children: "Create an account"
|
|
146
145
|
})
|
|
@@ -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,
|
|
8
|
+
const SignInPage = ({ workspace, notice, forgotPassword, signUp, 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);
|
|
@@ -26,8 +26,8 @@ const SignInPage = ({ workspace, notice, onForgotPassword, onSignUp, onBrowse, o
|
|
|
26
26
|
workspace,
|
|
27
27
|
notice,
|
|
28
28
|
methods: signInMethods,
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
forgotPassword,
|
|
30
|
+
signUp,
|
|
31
31
|
onBrowse,
|
|
32
32
|
providers: signInProviders,
|
|
33
33
|
onContinueWith: (provider) => void attempt(() => signInSocial(provider), signInMessage),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RouteLink } from "../primitives/route-link.js";
|
|
1
2
|
import { useAuth } from "./auth-provider.js";
|
|
2
3
|
import { Button, buttonClass } from "../primitives/button.js";
|
|
3
4
|
import { useSignInAddress } from "./use-sign-in-address.js";
|
|
@@ -19,21 +20,24 @@ const Benefits = ({ benefits }) => benefits && benefits.length > 0 ? /* @__PURE_
|
|
|
19
20
|
}) : null;
|
|
20
21
|
const Actions = ({ prompt }) => {
|
|
21
22
|
const { canSignUp } = useAuth();
|
|
22
|
-
const {
|
|
23
|
-
|
|
23
|
+
const { link } = useSignInAddress();
|
|
24
|
+
const primary = buttonClass({ variant: "primary" });
|
|
25
|
+
const signUp = prompt.signUp ? /* @__PURE__ */ jsx("a", {
|
|
24
26
|
href: prompt.signUp.href,
|
|
25
27
|
target: "_blank",
|
|
26
28
|
rel: "noreferrer",
|
|
27
|
-
className:
|
|
29
|
+
className: primary,
|
|
28
30
|
children: prompt.signUp.label
|
|
29
|
-
}) : canSignUp && /* @__PURE__ */ jsx(
|
|
30
|
-
|
|
31
|
+
}) : canSignUp && /* @__PURE__ */ jsx(RouteLink, {
|
|
32
|
+
to: link("sign-up"),
|
|
33
|
+
className: primary,
|
|
31
34
|
children: "Create an account"
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
});
|
|
36
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(RouteLink, {
|
|
37
|
+
to: link(),
|
|
38
|
+
className: signUp ? buttonClass() : primary,
|
|
35
39
|
children: "Sign in"
|
|
36
|
-
})] });
|
|
40
|
+
}), signUp] });
|
|
37
41
|
};
|
|
38
42
|
/**
|
|
39
43
|
* What a visitor who is not signed in is told about something they could use once signed in: the prompt's title, what
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CodeForm } from "./code-form.js";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/auth/sign-up-code-form.tsx
|
|
4
|
+
/** The second step of making an account: the emailed code on its own, before the name and password. */
|
|
5
|
+
const SignUpCodeForm = ({ email, onBack, ...rest }) => /* @__PURE__ */ jsx(CodeForm, {
|
|
6
|
+
title: "Check your email",
|
|
7
|
+
description: "Enter the code we sent to create your account.",
|
|
8
|
+
explanation: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9
|
+
"A 6-digit code is on its way to ",
|
|
10
|
+
/* @__PURE__ */ jsx("span", {
|
|
11
|
+
className: "font-medium text-foreground",
|
|
12
|
+
children: email
|
|
13
|
+
}),
|
|
14
|
+
". It works for 5 minutes, once. If the address has an account already, the email says so instead."
|
|
15
|
+
] }),
|
|
16
|
+
submitLabel: "Continue",
|
|
17
|
+
onChangeAddress: onBack,
|
|
18
|
+
changeAddressLabel: "Use another address",
|
|
19
|
+
...rest
|
|
20
|
+
});
|
|
21
|
+
//#endregion
|
|
22
|
+
export { SignUpCodeForm };
|