@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.
Files changed (92) hide show
  1. package/dist/account/two-factor-setup.js +6 -5
  2. package/dist/app/app.js +2 -1
  3. package/dist/app/create/create-page.js +1 -0
  4. package/dist/app/create/create-record-form.js +2 -1
  5. package/dist/app/displays/custom-display.js +1 -1
  6. package/dist/app/displays/map-display.js +50 -34
  7. package/dist/app/displays/map-groups.js +12 -3
  8. package/dist/app/displays/measured-heights.js +23 -0
  9. package/dist/app/displays/record-card.js +2 -2
  10. package/dist/app/displays/virtual-cards.js +12 -14
  11. package/dist/app/field-editor.js +9 -1
  12. package/dist/app/index.html +2 -2
  13. package/dist/app/list-page.js +2 -2
  14. package/dist/app/map/approximate-position.js +76 -0
  15. package/dist/app/map/area-features.js +80 -0
  16. package/dist/app/map/maplibre.js +11 -0
  17. package/dist/app/map/position-areas.js +78 -0
  18. package/dist/app/map/record-map.js +2 -7
  19. package/dist/app/map/record-position.js +5 -4
  20. package/dist/app/map/records-map.js +24 -7
  21. package/dist/app/map/theme-color.js +23 -0
  22. package/dist/app/meta-gate.js +1 -1
  23. package/dist/app/nav-recent.js +1 -1
  24. package/dist/app/pages/blocks/card-frame.js +2 -2
  25. package/dist/app/pages/blocks/grid.js +10 -2
  26. package/dist/app/pages/blocks/link.js +1 -1
  27. package/dist/app/pages/blocks/page-block.js +1 -1
  28. package/dist/app/pages/blocks/stat.js +9 -5
  29. package/dist/app/pages/field-value.js +1 -1
  30. package/dist/app/record-page.js +8 -4
  31. package/dist/app/router.js +7 -8
  32. package/dist/app/saved/saved-records.js +1 -1
  33. package/dist/app/search/search-model.js +1 -1
  34. package/dist/app/searches/use-save-search.js +4 -3
  35. package/dist/app/source-notice.js +2 -2
  36. package/dist/app/use-record-editor.js +5 -3
  37. package/dist/app-shell/app-shell.d.ts +1 -0
  38. package/dist/app-shell/app-shell.js +6 -4
  39. package/dist/app-shell/page-scroll.js +21 -4
  40. package/dist/app-shell/sidebar.d.ts +1 -0
  41. package/dist/app-shell/sidebar.js +4 -4
  42. package/dist/app-shell/top-bar.js +4 -4
  43. package/dist/auth/auth-gate.js +18 -9
  44. package/dist/auth/auth-provider.js +1 -0
  45. package/dist/auth/code-form.js +77 -0
  46. package/dist/auth/email-code-form.js +18 -77
  47. package/dist/auth/forgot-password-form.js +10 -10
  48. package/dist/auth/forgot-password-page.js +2 -2
  49. package/dist/auth/resend-code-button.js +29 -0
  50. package/dist/auth/sign-in-form.js +7 -8
  51. package/dist/auth/sign-in-page.js +3 -3
  52. package/dist/auth/sign-in-prompt.js +13 -9
  53. package/dist/auth/sign-up-code-form.js +22 -0
  54. package/dist/auth/sign-up-details-form.js +81 -111
  55. package/dist/auth/sign-up-page.js +17 -8
  56. package/dist/auth/two-factor-form.js +3 -3
  57. package/dist/auth/use-sign-in-address.js +21 -8
  58. package/dist/index.d.ts +2 -1
  59. package/dist/index.js +2 -1
  60. package/dist/lib/flip-guard.js +43 -0
  61. package/dist/lib/page-scroll-lock.js +20 -0
  62. package/dist/lib/use-focus-trap.js +4 -4
  63. package/dist/organizations/invitation-page.js +7 -6
  64. package/dist/package.js +1 -1
  65. package/dist/primitives/otp-input.d.ts +21 -0
  66. package/dist/primitives/otp-input.js +117 -0
  67. package/dist/primitives/otp-value.js +23 -0
  68. package/dist/primitives/route-link.d.ts +1 -0
  69. package/dist/primitives/route-link.js +20 -0
  70. package/dist/primitives/textarea.js +36 -0
  71. package/dist/record-view/field.js +56 -53
  72. package/dist/shell/assets/app-DJAZP1yB.js +66 -0
  73. package/dist/shell/assets/index-C5pYkUfv.css +2 -0
  74. package/dist/shell/assets/{index-CrtAqGCx.js → index-LBhMBiK4.js} +1 -1
  75. package/dist/shell/assets/maplibre-Dy0ZTnFZ.js +814 -0
  76. package/dist/shell/assets/record-map-BiWSbqqT.js +1 -0
  77. package/dist/shell/assets/records-map-DabYc3ad.js +1 -0
  78. package/dist/shell/assets/shared-protobase-ui-BURelnPG.js +1 -0
  79. package/dist/shell/index.html +6 -6
  80. package/dist/shell/protobase.shell.json +1 -1
  81. package/dist/styles.css +15 -0
  82. package/dist/toasts/toast.js +22 -6
  83. package/package.json +4 -4
  84. package/dist/auth/code-input.js +0 -17
  85. package/dist/auth/sign-in-address.js +0 -47
  86. package/dist/shell/assets/app-CpFoFNtG.js +0 -66
  87. package/dist/shell/assets/index-ibaHR5wQ.css +0 -2
  88. package/dist/shell/assets/maplibre-gl-B_TZ9n3D.js +0 -1
  89. package/dist/shell/assets/record-map-B2mkMEcE.js +0 -814
  90. package/dist/shell/assets/records-map-CAj21Al1.js +0 -1
  91. package/dist/shell/assets/shared-protobase-ui-D942ARkw.js +0 -1
  92. /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, onSignIn, organizations, roles, theme, sticky }) => {
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
- }), onSignIn && /* @__PURE__ */ jsxs("button", {
66
- type: "button",
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 px-2 md:gap-4 md:px-5"),
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-0.5"),
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-0.5"),
47
+ className: cn(iconButton, !onMenu && "-ml-3"),
48
48
  children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-5" })
49
49
  }),
50
50
  /* @__PURE__ */ jsx(Breadcrumb, {
@@ -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
- onSignIn: () => open()
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, { onBack: backTo("sign-in") });
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-code" ? signUpEmail : void 0,
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
- onForgotPassword: passwordReset ? () => open("forgot-password") : void 0,
225
+ forgotPassword: passwordReset ? link("forgot-password") : void 0,
217
226
  onCodeSent: (email) => {
218
227
  setCodeEmail(email);
219
228
  open("email-code");
220
229
  },
221
- onSignUp: canSignUp ? () => open("sign-up") : void 0,
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 { Button } from "../primitives/button.js";
2
- import { CodeInput } from "./code-input.js";
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, onSubmit, onResend, onBack, busy, error, notice }) => {
9
- const [code, setCode] = useState("");
10
- return /* @__PURE__ */ jsxs(AuthLayout, {
11
- title: "Check your email",
12
- description: "Enter the code we sent to sign in.",
13
- children: [
14
- /* @__PURE__ */ jsxs("p", {
15
- className: "mb-4 text-[13px] text-muted-foreground",
16
- children: [
17
- "If ",
18
- /* @__PURE__ */ jsx("span", {
19
- className: "font-medium text-foreground",
20
- children: email
21
- }),
22
- " has an account, a 6-digit code is on its way. It works for 5 minutes, once."
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, onBack, busy, error, sentTo }) => {
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(Button, {
22
- variant: "primary",
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(Button, {
65
- variant: "ghost",
66
- onClick: onBack,
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 = ({ onBack }) => {
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
- onBack,
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 = [], onForgotPassword, providers = [], onContinueWith, onSignUp, onBrowse }) => {
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 && onForgotPassword && /* @__PURE__ */ jsx("button", {
129
- type: "button",
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
- onSignUp && /* @__PURE__ */ jsxs("p", {
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("button", {
142
- type: "button",
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, onForgotPassword, onSignUp, onBrowse, onCodeSent, error: initialError }) => {
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
- onForgotPassword,
30
- onSignUp,
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 { open } = useSignInAddress();
23
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [prompt.signUp ? /* @__PURE__ */ jsx("a", {
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: buttonClass(),
29
+ className: primary,
28
30
  children: prompt.signUp.label
29
- }) : canSignUp && /* @__PURE__ */ jsx(Button, {
30
- onClick: () => open("sign-up"),
31
+ }) : canSignUp && /* @__PURE__ */ jsx(RouteLink, {
32
+ to: link("sign-up"),
33
+ className: primary,
31
34
  children: "Create an account"
32
- }), /* @__PURE__ */ jsx(Button, {
33
- variant: "primary",
34
- onClick: () => open(),
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 };