@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.
@@ -0,0 +1,130 @@
1
+ import { Input } from "../primitives/input.js";
2
+ import { Button } from "../primitives/button.js";
3
+ import { CodeInput } from "./code-input.js";
4
+ import { AuthLayout } from "./auth-layout.js";
5
+ import { useId, useState } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/auth/sign-up-details-form.tsx
8
+ /** The second step of making an account: the emailed code, the person's name and how they will sign in. */
9
+ const SignUpDetailsForm = ({ email, onSubmit, onResend, onBack, methods, busy, error, notice }) => {
10
+ const [code, setCode] = useState("");
11
+ const [name, setName] = useState("");
12
+ const [password, setPassword] = useState("");
13
+ const passwordHint = useId();
14
+ const withPassword = methods.includes("password");
15
+ const passwordOptional = methods.includes("emailCode");
16
+ return /* @__PURE__ */ jsxs(AuthLayout, {
17
+ title: "Check your email",
18
+ description: "Enter the code we sent to create your account.",
19
+ children: [
20
+ /* @__PURE__ */ jsxs("p", {
21
+ className: "mb-4 text-[13px] text-muted-foreground",
22
+ children: [
23
+ "A 6-digit code is on its way to ",
24
+ /* @__PURE__ */ jsx("span", {
25
+ className: "font-medium text-foreground",
26
+ children: email
27
+ }),
28
+ ". It works for 5 minutes, once. If the address has an account already, the email says so instead."
29
+ ]
30
+ }),
31
+ notice && /* @__PURE__ */ jsx("p", {
32
+ role: "status",
33
+ className: "mb-4 rounded-md bg-success-soft px-3 py-2 text-xs font-medium text-success-text",
34
+ children: notice
35
+ }),
36
+ /* @__PURE__ */ jsxs("form", {
37
+ className: "flex flex-col gap-4",
38
+ onSubmit: (event) => {
39
+ event.preventDefault();
40
+ onSubmit({
41
+ code: code.trim(),
42
+ name: name.trim(),
43
+ ...withPassword && password && { password }
44
+ });
45
+ },
46
+ children: [
47
+ /* @__PURE__ */ jsxs("label", {
48
+ className: "flex flex-col gap-1.5 text-[13px] font-medium",
49
+ children: ["Code", /* @__PURE__ */ jsx(CodeInput, {
50
+ name: "code",
51
+ required: true,
52
+ autoFocus: true,
53
+ value: code,
54
+ invalid: Boolean(error),
55
+ onChange: (event) => setCode(event.target.value)
56
+ })]
57
+ }),
58
+ /* @__PURE__ */ jsxs("label", {
59
+ className: "flex flex-col gap-1.5 text-[13px] font-medium",
60
+ children: ["Your name", /* @__PURE__ */ jsx(Input, {
61
+ name: "name",
62
+ autoComplete: "name",
63
+ required: true,
64
+ maxLength: 200,
65
+ value: name,
66
+ onChange: (event) => setName(event.target.value),
67
+ className: "min-h-6"
68
+ })]
69
+ }),
70
+ withPassword && /* @__PURE__ */ jsxs("div", {
71
+ className: "flex flex-col gap-1.5",
72
+ children: [/* @__PURE__ */ jsxs("label", {
73
+ className: "flex flex-col gap-1.5 text-[13px] font-medium",
74
+ children: [passwordOptional ? "Password (optional)" : "Password", /* @__PURE__ */ jsx(Input, {
75
+ type: "password",
76
+ name: "password",
77
+ autoComplete: "new-password",
78
+ "aria-describedby": passwordHint,
79
+ required: !passwordOptional,
80
+ minLength: 12,
81
+ maxLength: 128,
82
+ value: password,
83
+ onChange: (event) => setPassword(event.target.value),
84
+ className: "min-h-6"
85
+ })]
86
+ }), /* @__PURE__ */ jsxs("p", {
87
+ id: passwordHint,
88
+ className: "text-xs text-muted-foreground",
89
+ children: [
90
+ "At least ",
91
+ 12,
92
+ " characters.",
93
+ passwordOptional && " Without one, you sign in with a code we email you."
94
+ ]
95
+ })]
96
+ }),
97
+ error && /* @__PURE__ */ jsx("p", {
98
+ role: "alert",
99
+ className: "text-xs font-medium text-danger-text",
100
+ children: error
101
+ }),
102
+ /* @__PURE__ */ jsx(Button, {
103
+ variant: "primary",
104
+ type: "submit",
105
+ loading: busy,
106
+ className: "min-h-10",
107
+ children: "Create account"
108
+ }),
109
+ /* @__PURE__ */ jsxs("div", {
110
+ className: "flex justify-between gap-2",
111
+ children: [/* @__PURE__ */ jsx(Button, {
112
+ variant: "ghost",
113
+ onClick: onBack,
114
+ className: "min-h-10",
115
+ children: "Use another address"
116
+ }), /* @__PURE__ */ jsx(Button, {
117
+ variant: "ghost",
118
+ onClick: onResend,
119
+ disabled: busy,
120
+ className: "min-h-10",
121
+ children: "Send a new code"
122
+ })]
123
+ })
124
+ ]
125
+ })
126
+ ]
127
+ });
128
+ };
129
+ //#endregion
130
+ export { SignUpDetailsForm };
@@ -0,0 +1,74 @@
1
+ import { Input } from "../primitives/input.js";
2
+ import { Button } from "../primitives/button.js";
3
+ import { AuthLayout } from "./auth-layout.js";
4
+ import { useState } from "react";
5
+ import { Mail } from "lucide-react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/auth/sign-up-email-form.tsx
8
+ /** The first step of making an account: the address, which gets a code to prove it is theirs. */
9
+ const SignUpEmailForm = ({ onSubmit, onSignIn, onBrowse, busy, error, workspace }) => {
10
+ const [email, setEmail] = useState("");
11
+ return /* @__PURE__ */ jsxs(AuthLayout, {
12
+ title: "Create an account",
13
+ description: workspace,
14
+ children: [/* @__PURE__ */ jsxs("form", {
15
+ className: "flex flex-col gap-4",
16
+ onSubmit: (event) => {
17
+ event.preventDefault();
18
+ onSubmit(email.trim());
19
+ },
20
+ children: [
21
+ /* @__PURE__ */ jsxs("label", {
22
+ className: "flex flex-col gap-1.5 text-[13px] font-medium",
23
+ children: ["Email", /* @__PURE__ */ jsx(Input, {
24
+ type: "email",
25
+ name: "email",
26
+ autoComplete: "email",
27
+ required: true,
28
+ autoFocus: true,
29
+ value: email,
30
+ invalid: Boolean(error),
31
+ onChange: (event) => setEmail(event.target.value),
32
+ className: "min-h-6"
33
+ })]
34
+ }),
35
+ /* @__PURE__ */ jsx("p", {
36
+ className: "text-[13px] text-muted-foreground",
37
+ children: "We email you a code to make sure the address is yours."
38
+ }),
39
+ error && /* @__PURE__ */ jsx("p", {
40
+ role: "alert",
41
+ className: "text-xs font-medium text-danger-text",
42
+ children: error
43
+ }),
44
+ /* @__PURE__ */ jsxs(Button, {
45
+ variant: "primary",
46
+ type: "submit",
47
+ loading: busy,
48
+ className: "min-h-10",
49
+ children: [/* @__PURE__ */ jsx(Mail, { className: "size-4" }), "Email me a code"]
50
+ }),
51
+ /* @__PURE__ */ jsxs("p", {
52
+ className: "text-center text-[13px] text-muted-foreground",
53
+ children: [
54
+ "Have an account?",
55
+ " ",
56
+ /* @__PURE__ */ jsx("button", {
57
+ type: "button",
58
+ onClick: onSignIn,
59
+ className: "font-medium text-primary-text hover:underline",
60
+ children: "Sign in"
61
+ })
62
+ ]
63
+ })
64
+ ]
65
+ }), onBrowse && /* @__PURE__ */ jsx("button", {
66
+ type: "button",
67
+ onClick: onBrowse,
68
+ className: "mt-4 w-full text-center text-[13px] font-medium text-muted-foreground hover:text-foreground hover:underline",
69
+ children: "Continue without an account"
70
+ })]
71
+ });
72
+ };
73
+ //#endregion
74
+ export { SignUpEmailForm };
@@ -0,0 +1,59 @@
1
+ import { authMessage } from "./auth-messages.js";
2
+ import { useAuth } from "./auth-provider.js";
3
+ import { SignUpDetailsForm } from "./sign-up-details-form.js";
4
+ import { SignUpEmailForm } from "./sign-up-email-form.js";
5
+ import { useState } from "react";
6
+ import { AuthError } from "@protobase/client";
7
+ import { jsx } from "react/jsx-runtime";
8
+ //#region src/auth/sign-up-page.tsx
9
+ /**
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
+ * password. The new account is signed in, and sets up what the sign-in policy requires as after any sign-in.
12
+ */
13
+ const SignUpPage = ({ workspace, email, onCodeSent, onBack, onSignIn, onBrowse }) => {
14
+ const { signInMethods, sendSignUpCode, signUp } = useAuth();
15
+ const [busy, setBusy] = useState(false);
16
+ const [error, setError] = useState();
17
+ const [notice, setNotice] = useState();
18
+ const attempt = async (step) => {
19
+ setBusy(true);
20
+ setError(void 0);
21
+ setNotice(void 0);
22
+ try {
23
+ await step();
24
+ } catch (failure) {
25
+ if (!(failure instanceof AuthError)) throw failure;
26
+ setError(authMessage(failure));
27
+ }
28
+ setBusy(false);
29
+ };
30
+ if (email === void 0) return /* @__PURE__ */ jsx(SignUpEmailForm, {
31
+ workspace,
32
+ busy,
33
+ error,
34
+ onSignIn,
35
+ onBrowse,
36
+ onSubmit: (address) => void attempt(async () => {
37
+ await sendSignUpCode(address);
38
+ onCodeSent(address);
39
+ })
40
+ });
41
+ return /* @__PURE__ */ jsx(SignUpDetailsForm, {
42
+ email,
43
+ methods: signInMethods,
44
+ busy,
45
+ error,
46
+ notice,
47
+ onBack,
48
+ onResend: () => void attempt(async () => {
49
+ await sendSignUpCode(email);
50
+ setNotice("A new code is on its way. Only the newest one works.");
51
+ }),
52
+ onSubmit: (details) => void attempt(() => signUp({
53
+ email,
54
+ ...details
55
+ }))
56
+ });
57
+ };
58
+ //#endregion
59
+ export { SignUpPage };
@@ -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
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.14.6";
2
+ var version = "0.15.0";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };