@protobase/ui 0.3.5 → 0.5.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/account-pages.js +20 -0
- package/dist/account/account-section.js +31 -0
- package/dist/account/account-security-page.js +59 -0
- package/dist/account/backup-codes.js +44 -0
- package/dist/account/passkeys-section.js +141 -0
- package/dist/account/password-dialog.js +58 -0
- package/dist/account/sign-in-policy-form.js +108 -0
- package/dist/account/sign-in-policy-labels.js +76 -0
- package/dist/account/sign-in-policy-page.js +72 -0
- package/dist/account/staff-sign-in-log.js +62 -0
- package/dist/account/totp-qr.js +37 -0
- package/dist/account/two-factor-section.js +123 -0
- package/dist/account/two-factor-setup.js +188 -0
- package/dist/activity-chart/activity-chart.js +1 -1
- package/dist/activity-chart/activity-tooltip.js +1 -1
- package/dist/app/app.js +37 -15
- package/dist/app/assistant-shell.js +3 -2
- package/dist/app/create/create-page.js +1 -1
- package/dist/app/delete/use-delete-flow.js +1 -1
- package/dist/app/error-banner.js +1 -1
- package/dist/app/list-page.js +1 -1
- package/dist/app/list-search.js +1 -1
- package/dist/app/meta-gate.js +1 -1
- package/dist/app/pages/blocks/modal-form.js +3 -3
- package/dist/app/pages/merge-ui.d.ts +9 -0
- package/dist/app/pages/merge-ui.js +45 -0
- package/dist/app/pages/project-ui-provider.js +15 -0
- package/dist/app/pages/project-ui.d.ts +6 -3
- package/dist/app/pages/project-ui.js +3 -7
- package/dist/app/pages/record-table.js +1 -1
- package/dist/app/pages/use-run-action.js +1 -1
- package/dist/app/record-page.js +5 -5
- package/dist/app/runtime/runtime-update.js +1 -1
- package/dist/app/sensitive-field.js +1 -1
- package/dist/app/staff-banner.js +67 -0
- package/dist/app/use-record-editor.js +1 -1
- package/dist/app-shell/breadcrumb.js +1 -1
- package/dist/app-shell/global-search.js +1 -1
- package/dist/app-shell/profile-menu.js +1 -1
- package/dist/app-shell/sidebar-group.js +1 -1
- package/dist/app-shell/top-bar.js +1 -1
- package/dist/assistant-dock/assistant-dock.d.ts +8 -2
- package/dist/assistant-dock/assistant-dock.js +20 -13
- package/dist/assistant-dock/index.d.ts +3 -1
- package/dist/assistant-dock/part-view.d.ts +1 -1
- package/dist/assistant-dock/part-view.js +3 -1
- package/dist/assistant-dock/use-backend-data.d.ts +23 -0
- package/dist/assistant-dock/use-backend-data.js +67 -0
- package/dist/assistant-dock/widget-view.d.ts +1 -0
- package/dist/assistant-dock/widget-view.js +44 -0
- package/dist/auth/auth-gate.js +42 -4
- package/dist/auth/auth-messages.js +48 -0
- package/dist/auth/auth-provider.js +40 -14
- package/dist/auth/code-input.js +17 -0
- package/dist/auth/email-code-form.js +81 -0
- package/dist/auth/email-code-page.js +40 -0
- package/dist/auth/first-run-page.js +1 -1
- package/dist/auth/forgot-password-page.js +1 -1
- package/dist/auth/reset-password-page.js +1 -1
- package/dist/auth/setup-required-form.js +43 -0
- package/dist/auth/setup-required-page.js +51 -0
- package/dist/auth/sign-in-form.js +53 -20
- package/dist/auth/sign-in-page.js +14 -13
- package/dist/auth/staff-link.js +30 -0
- package/dist/auth/staff-sign-in-form.js +93 -0
- package/dist/auth/staff-sign-in-page.js +41 -0
- package/dist/auth/two-factor-form.js +137 -0
- package/dist/auth/two-factor-page.js +39 -0
- package/dist/auth/user-to-shell.js +5 -2
- package/dist/chat/composer.js +1 -1
- package/dist/data/api-provider.d.ts +1 -1
- package/dist/data/api-provider.js +1 -1
- package/dist/data/use-aggregates.js +1 -1
- package/dist/data/use-list.js +1 -1
- package/dist/data/use-update-record.js +1 -1
- package/dist/data-grid/cells.js +3 -3
- package/dist/data-grid/grid-header.js +2 -2
- package/dist/data-grid/grid-row.js +1 -1
- package/dist/data-grid/header-menu.js +1 -1
- package/dist/data-grid/row-menu.js +1 -1
- package/dist/diff-view/diff-view.js +1 -1
- package/dist/filter-panel/active-filter-chips.js +1 -1
- package/dist/filter-panel/facet-group.js +1 -1
- package/dist/filter-panel/filter-menu.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -2
- package/dist/inline-child-table/inline-child-table.js +1 -1
- package/dist/inline-child-table/line-menu.js +1 -1
- package/dist/inline-child-table/line-row.js +2 -2
- package/dist/inline-child-table/totals-footer.js +1 -1
- package/dist/live/use-filter-config.js +1 -1
- package/dist/primitives/checkbox.js +1 -1
- package/dist/primitives/sheet.js +1 -1
- package/dist/primitives/spinner.js +1 -1
- package/dist/record-view/field.js +1 -1
- package/dist/record-view/record-sidebar.js +1 -1
- package/dist/side-panel/side-panel.js +1 -1
- package/dist/step-list/step-list.js +1 -1
- package/dist/toasts/toast.js +1 -1
- package/package.json +6 -5
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { Input } from "../primitives/input.js";
|
|
3
|
+
import { Button } from "../primitives/button.js";
|
|
4
|
+
import { AuthLayout } from "./auth-layout.js";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/auth/staff-sign-in-form.tsx
|
|
8
|
+
const minimumReason = 10;
|
|
9
|
+
/** For staff of the operator: sign in as a person of this app, with a reason the app's admins see in their log. */
|
|
10
|
+
const StaffSignInForm = ({ operator, email: givenEmail = "", reason: givenReason = "", onSubmit, onBack, busy, error }) => {
|
|
11
|
+
const [email, setEmail] = useState(givenEmail);
|
|
12
|
+
const [reason, setReason] = useState(givenReason);
|
|
13
|
+
if (!operator) return /* @__PURE__ */ jsxs(AuthLayout, {
|
|
14
|
+
title: "Staff sign-in",
|
|
15
|
+
description: "Staff of the team that runs this app cannot sign in as people here.",
|
|
16
|
+
children: [error && /* @__PURE__ */ jsx("p", {
|
|
17
|
+
role: "alert",
|
|
18
|
+
className: "mb-4 text-xs font-medium text-danger-text",
|
|
19
|
+
children: error
|
|
20
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
21
|
+
variant: "primary",
|
|
22
|
+
className: "min-h-10 w-full",
|
|
23
|
+
onClick: onBack,
|
|
24
|
+
children: "Back to sign in"
|
|
25
|
+
})]
|
|
26
|
+
});
|
|
27
|
+
return /* @__PURE__ */ jsx(AuthLayout, {
|
|
28
|
+
title: "Sign in as a person",
|
|
29
|
+
description: `For staff of ${operator}, to help someone with this app.`,
|
|
30
|
+
children: /* @__PURE__ */ jsxs("form", {
|
|
31
|
+
className: "flex flex-col gap-4",
|
|
32
|
+
onSubmit: (event) => {
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
onSubmit(email.trim(), reason.trim());
|
|
35
|
+
},
|
|
36
|
+
children: [
|
|
37
|
+
/* @__PURE__ */ jsxs("label", {
|
|
38
|
+
className: "flex flex-col gap-1.5 text-[13px] font-medium",
|
|
39
|
+
children: ["Their email", /* @__PURE__ */ jsx(Input, {
|
|
40
|
+
type: "email",
|
|
41
|
+
name: "email",
|
|
42
|
+
required: true,
|
|
43
|
+
autoFocus: !givenEmail,
|
|
44
|
+
value: email,
|
|
45
|
+
onChange: (event) => setEmail(event.target.value),
|
|
46
|
+
className: "min-h-6"
|
|
47
|
+
})]
|
|
48
|
+
}),
|
|
49
|
+
/* @__PURE__ */ jsxs("label", {
|
|
50
|
+
className: "flex flex-col gap-1.5 text-[13px] font-medium",
|
|
51
|
+
children: ["Reason", /* @__PURE__ */ jsx("textarea", {
|
|
52
|
+
name: "reason",
|
|
53
|
+
required: true,
|
|
54
|
+
minLength: minimumReason,
|
|
55
|
+
rows: 3,
|
|
56
|
+
value: reason,
|
|
57
|
+
placeholder: "Ticket 4211: the invoice totals look wrong",
|
|
58
|
+
onChange: (event) => setReason(event.target.value),
|
|
59
|
+
className: cn("resize-y rounded-md border bg-background px-2.5 py-1.5 text-[13px] font-normal shadow-sm outline-none placeholder:text-faint-foreground focus:border-primary focus:ring-2 focus:ring-primary/20", error ? "border-danger" : "border-border-strong")
|
|
60
|
+
})]
|
|
61
|
+
}),
|
|
62
|
+
/* @__PURE__ */ jsxs("p", {
|
|
63
|
+
className: "text-xs text-muted-foreground",
|
|
64
|
+
children: [
|
|
65
|
+
"You sign in at ",
|
|
66
|
+
operator,
|
|
67
|
+
" with a passkey or a second step. The session is short, shows a banner, and cannot change their sign-in. The app's admins see who signed in as whom, and why."
|
|
68
|
+
]
|
|
69
|
+
}),
|
|
70
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
71
|
+
role: "alert",
|
|
72
|
+
className: "text-xs font-medium text-danger-text",
|
|
73
|
+
children: error
|
|
74
|
+
}),
|
|
75
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
76
|
+
variant: "primary",
|
|
77
|
+
type: "submit",
|
|
78
|
+
loading: busy,
|
|
79
|
+
className: "min-h-10",
|
|
80
|
+
children: ["Continue with ", operator]
|
|
81
|
+
}),
|
|
82
|
+
/* @__PURE__ */ jsx(Button, {
|
|
83
|
+
variant: "ghost",
|
|
84
|
+
onClick: onBack,
|
|
85
|
+
className: "min-h-10",
|
|
86
|
+
children: "Back to sign in"
|
|
87
|
+
})
|
|
88
|
+
]
|
|
89
|
+
})
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
//#endregion
|
|
93
|
+
export { StaffSignInForm };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { staffSignInMessage } from "./auth-messages.js";
|
|
2
|
+
import { useAuth } from "./auth-provider.js";
|
|
3
|
+
import { withoutStaffLink } from "./staff-link.js";
|
|
4
|
+
import { StaffSignInForm } from "./staff-sign-in-form.js";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { AuthError } from "@protobase/client";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
//#region src/auth/staff-sign-in-page.tsx
|
|
9
|
+
/** Starts a staff sign-in and leaves for the operator provider, which comes back to this page without the staff link. */
|
|
10
|
+
const StaffSignInPage = ({ link, onBack }) => {
|
|
11
|
+
const { session, staffSignIn } = useAuth();
|
|
12
|
+
const [busy, setBusy] = useState(false);
|
|
13
|
+
const [error, setError] = useState(link.error && staffSignInMessage(link.error));
|
|
14
|
+
const submit = async (email, reason) => {
|
|
15
|
+
setBusy(true);
|
|
16
|
+
setError(void 0);
|
|
17
|
+
try {
|
|
18
|
+
const callbackURL = `${window.location.origin}${withoutStaffLink(window.location.href)}`;
|
|
19
|
+
window.location.assign(await session.staff.start({
|
|
20
|
+
email,
|
|
21
|
+
reason,
|
|
22
|
+
callbackURL
|
|
23
|
+
}));
|
|
24
|
+
} catch (failure) {
|
|
25
|
+
if (!(failure instanceof AuthError)) throw failure;
|
|
26
|
+
setError(staffSignInMessage(failure));
|
|
27
|
+
setBusy(false);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
return /* @__PURE__ */ jsx(StaffSignInForm, {
|
|
31
|
+
operator: staffSignIn,
|
|
32
|
+
email: link.email,
|
|
33
|
+
reason: link.reason,
|
|
34
|
+
busy,
|
|
35
|
+
error,
|
|
36
|
+
onBack,
|
|
37
|
+
onSubmit: (email, reason) => void submit(email, reason)
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { StaffSignInPage };
|
|
@@ -0,0 +1,137 @@
|
|
|
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 { Checkbox } from "../primitives/checkbox.js";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/auth/two-factor-form.tsx
|
|
9
|
+
const instructions = {
|
|
10
|
+
totp: "Enter the 6-digit code from your authenticator app.",
|
|
11
|
+
otp: "Enter the 6-digit code we emailed you. It works for 5 minutes.",
|
|
12
|
+
backup: "Enter one of the backup codes you saved when you set up the authenticator app. Each works once."
|
|
13
|
+
};
|
|
14
|
+
/** The second step of a sign-in, for an account with two-factor authentication. */
|
|
15
|
+
const TwoFactorForm = ({ methods, onVerify, onSendCode, onBack, busy, error, codeSent, initialStep }) => {
|
|
16
|
+
const [step, setStep] = useState(initialStep ?? (methods.includes("totp") ? "totp" : "otp"));
|
|
17
|
+
const [code, setCode] = useState("");
|
|
18
|
+
const [trustDevice, setTrustDevice] = useState(false);
|
|
19
|
+
const waitingForEmail = step === "otp" && !codeSent;
|
|
20
|
+
const switchTo = (next) => {
|
|
21
|
+
setStep(next);
|
|
22
|
+
setCode("");
|
|
23
|
+
if (next === "otp" && !codeSent) onSendCode();
|
|
24
|
+
};
|
|
25
|
+
return /* @__PURE__ */ jsxs(AuthLayout, {
|
|
26
|
+
title: "Two-factor authentication",
|
|
27
|
+
description: "One more step to sign in.",
|
|
28
|
+
children: [waitingForEmail ? /* @__PURE__ */ jsxs("div", {
|
|
29
|
+
className: "flex flex-col gap-4",
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ jsx("p", {
|
|
32
|
+
className: "text-[13px] text-muted-foreground",
|
|
33
|
+
children: "We email you a code to finish signing in."
|
|
34
|
+
}),
|
|
35
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
36
|
+
role: "alert",
|
|
37
|
+
className: "text-xs font-medium text-danger-text",
|
|
38
|
+
children: error
|
|
39
|
+
}),
|
|
40
|
+
/* @__PURE__ */ jsx(Button, {
|
|
41
|
+
variant: "primary",
|
|
42
|
+
loading: busy,
|
|
43
|
+
onClick: onSendCode,
|
|
44
|
+
className: "min-h-10",
|
|
45
|
+
children: "Email me a code"
|
|
46
|
+
})
|
|
47
|
+
]
|
|
48
|
+
}) : /* @__PURE__ */ jsxs("form", {
|
|
49
|
+
className: "flex flex-col gap-4",
|
|
50
|
+
onSubmit: (event) => {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
onVerify({
|
|
53
|
+
method: step,
|
|
54
|
+
code: code.trim(),
|
|
55
|
+
trustDevice
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
children: [
|
|
59
|
+
/* @__PURE__ */ jsx("p", {
|
|
60
|
+
className: "text-[13px] text-muted-foreground",
|
|
61
|
+
children: instructions[step]
|
|
62
|
+
}),
|
|
63
|
+
/* @__PURE__ */ jsxs("label", {
|
|
64
|
+
className: "flex flex-col gap-1.5 text-[13px] font-medium",
|
|
65
|
+
children: [step === "backup" ? "Backup code" : "Code", step === "backup" ? /* @__PURE__ */ jsx(Input, {
|
|
66
|
+
name: "code",
|
|
67
|
+
autoComplete: "off",
|
|
68
|
+
spellCheck: false,
|
|
69
|
+
required: true,
|
|
70
|
+
autoFocus: true,
|
|
71
|
+
value: code,
|
|
72
|
+
invalid: Boolean(error),
|
|
73
|
+
onChange: (event) => setCode(event.target.value),
|
|
74
|
+
className: "min-h-6 font-mono"
|
|
75
|
+
}) : /* @__PURE__ */ jsx(CodeInput, {
|
|
76
|
+
name: "code",
|
|
77
|
+
required: true,
|
|
78
|
+
autoFocus: true,
|
|
79
|
+
value: code,
|
|
80
|
+
invalid: Boolean(error),
|
|
81
|
+
onChange: (event) => setCode(event.target.value)
|
|
82
|
+
})]
|
|
83
|
+
}),
|
|
84
|
+
/* @__PURE__ */ jsxs("label", {
|
|
85
|
+
className: "flex items-center gap-2 text-[13px] text-muted-foreground",
|
|
86
|
+
children: [/* @__PURE__ */ jsx(Checkbox, {
|
|
87
|
+
checked: trustDevice,
|
|
88
|
+
onChange: setTrustDevice,
|
|
89
|
+
label: "Trust this browser"
|
|
90
|
+
}), "Don't ask again in this browser for 30 days"]
|
|
91
|
+
}),
|
|
92
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
93
|
+
role: "alert",
|
|
94
|
+
className: "text-xs font-medium text-danger-text",
|
|
95
|
+
children: error
|
|
96
|
+
}),
|
|
97
|
+
/* @__PURE__ */ jsx(Button, {
|
|
98
|
+
variant: "primary",
|
|
99
|
+
type: "submit",
|
|
100
|
+
loading: busy,
|
|
101
|
+
className: "min-h-10",
|
|
102
|
+
children: "Verify"
|
|
103
|
+
})
|
|
104
|
+
]
|
|
105
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
106
|
+
className: "mt-4 flex flex-col items-center gap-2 border-t pt-4 text-[13px]",
|
|
107
|
+
children: [
|
|
108
|
+
step !== "totp" && methods.includes("totp") && /* @__PURE__ */ jsx("button", {
|
|
109
|
+
type: "button",
|
|
110
|
+
onClick: () => switchTo("totp"),
|
|
111
|
+
className: "font-medium text-primary-text hover:underline",
|
|
112
|
+
children: "Use the authenticator app"
|
|
113
|
+
}),
|
|
114
|
+
step !== "otp" && methods.includes("otp") && /* @__PURE__ */ jsx("button", {
|
|
115
|
+
type: "button",
|
|
116
|
+
onClick: () => switchTo("otp"),
|
|
117
|
+
className: "font-medium text-primary-text hover:underline",
|
|
118
|
+
children: "Email me a code instead"
|
|
119
|
+
}),
|
|
120
|
+
step !== "backup" && methods.includes("totp") && /* @__PURE__ */ jsx("button", {
|
|
121
|
+
type: "button",
|
|
122
|
+
onClick: () => switchTo("backup"),
|
|
123
|
+
className: "font-medium text-primary-text hover:underline",
|
|
124
|
+
children: "Use a backup code"
|
|
125
|
+
}),
|
|
126
|
+
/* @__PURE__ */ jsx("button", {
|
|
127
|
+
type: "button",
|
|
128
|
+
onClick: onBack,
|
|
129
|
+
className: "font-medium text-muted-foreground hover:underline",
|
|
130
|
+
children: "Back to sign in"
|
|
131
|
+
})
|
|
132
|
+
]
|
|
133
|
+
})]
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
//#endregion
|
|
137
|
+
export { TwoFactorForm };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { authMessage } from "./auth-messages.js";
|
|
2
|
+
import { useAuth } from "./auth-provider.js";
|
|
3
|
+
import { TwoFactorForm } from "./two-factor-form.js";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { AuthError } from "@protobase/client";
|
|
6
|
+
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
//#region src/auth/two-factor-page.tsx
|
|
8
|
+
/** The second step after a password or an emailed code, with the methods the server named. */
|
|
9
|
+
const TwoFactorPage = ({ methods, onBack }) => {
|
|
10
|
+
const { verifyTwoFactor, session } = useAuth();
|
|
11
|
+
const [busy, setBusy] = useState(false);
|
|
12
|
+
const [error, setError] = useState();
|
|
13
|
+
const [codeSent, setCodeSent] = useState(false);
|
|
14
|
+
const attempt = async (step) => {
|
|
15
|
+
setBusy(true);
|
|
16
|
+
setError(void 0);
|
|
17
|
+
try {
|
|
18
|
+
await step();
|
|
19
|
+
} catch (failure) {
|
|
20
|
+
if (!(failure instanceof AuthError)) throw failure;
|
|
21
|
+
setError(authMessage(failure));
|
|
22
|
+
}
|
|
23
|
+
setBusy(false);
|
|
24
|
+
};
|
|
25
|
+
return /* @__PURE__ */ jsx(TwoFactorForm, {
|
|
26
|
+
methods,
|
|
27
|
+
busy,
|
|
28
|
+
error,
|
|
29
|
+
codeSent,
|
|
30
|
+
onBack,
|
|
31
|
+
onVerify: (input) => void attempt(() => verifyTwoFactor(input)),
|
|
32
|
+
onSendCode: () => void attempt(async () => {
|
|
33
|
+
await session.sendTwoFactorCode();
|
|
34
|
+
setCodeSent(true);
|
|
35
|
+
})
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
export { TwoFactorPage };
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
//#region src/auth/user-to-shell.ts
|
|
2
2
|
const hueOf = (text) => [...text].reduce((sum, char) => (sum * 31 + char.charCodeAt(0)) % 360, 7);
|
|
3
3
|
const initialsOf = (name) => name.split(/[\s@.]+/).filter(Boolean).slice(0, 2).map((part) => part[0].toUpperCase()).join("");
|
|
4
|
+
const rolesOf = (user) => (user.role ?? "user").split(",").map((role) => role.trim());
|
|
5
|
+
/** The user holds the `admin` role, among others perhaps. */
|
|
6
|
+
const isAdmin = (user) => rolesOf(user).includes("admin");
|
|
4
7
|
/** What the sidebar's profile menu shows for a signed-in user. */
|
|
5
8
|
const userToShell = (user) => {
|
|
6
|
-
const roles = (user
|
|
9
|
+
const roles = rolesOf(user);
|
|
7
10
|
const admin = roles.includes("admin");
|
|
8
11
|
return {
|
|
9
12
|
name: user.name || user.email,
|
|
@@ -15,4 +18,4 @@ const userToShell = (user) => {
|
|
|
15
18
|
};
|
|
16
19
|
};
|
|
17
20
|
//#endregion
|
|
18
|
-
export { userToShell };
|
|
21
|
+
export { isAdmin, userToShell };
|
package/dist/chat/composer.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Input } from "../primitives/input.js";
|
|
2
2
|
import { Button } from "../primitives/button.js";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
4
|
import { SendHorizontal } from "lucide-react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
//#region src/chat/composer.tsx
|
|
7
7
|
/** The text box under a chat. Enter or the button sends the trimmed text and clears the box. */
|
|
8
8
|
const Composer = ({ onSend, busy, placeholder, inputRef }) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
|
-
import { QueryClient } from "@tanstack/react-query";
|
|
3
2
|
import { Client, ClientOptions } from "@protobase/client";
|
|
3
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
4
4
|
//#region src/data/api-provider.d.ts
|
|
5
5
|
export declare const useClient: () => {
|
|
6
6
|
list: <R extends import("@protobase/client").ResourceRef>(resource: R, params?: import("@protobase/client").ListParams) => Promise<import("@protobase/client").ListPage<import("@protobase/client").RecordOf<R>>>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { keys } from "./query-keys.js";
|
|
2
2
|
import { createContext, useContext, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { createClient } from "@protobase/client";
|
|
3
4
|
import { jsx } from "react/jsx-runtime";
|
|
4
5
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
|
-
import { createClient } from "@protobase/client";
|
|
6
6
|
//#region src/data/api-provider.tsx
|
|
7
7
|
const ApiContext = createContext(null);
|
|
8
8
|
const useClient = () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { keys } from "./query-keys.js";
|
|
2
2
|
import { useClient } from "./api-provider.js";
|
|
3
|
-
import { useQuery } from "@tanstack/react-query";
|
|
4
3
|
import { filterText } from "@protobase/client";
|
|
4
|
+
import { useQuery } from "@tanstack/react-query";
|
|
5
5
|
//#region src/data/use-aggregates.ts
|
|
6
6
|
const useFacets = (resource, field, filter, enabled = true) => {
|
|
7
7
|
const client = useClient();
|
package/dist/data/use-list.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { keys } from "./query-keys.js";
|
|
2
2
|
import { useClient } from "./api-provider.js";
|
|
3
|
-
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
4
3
|
import { filterText } from "@protobase/client";
|
|
4
|
+
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
5
5
|
//#region src/data/use-list.ts
|
|
6
6
|
/** Infinite pages of a resource, following `next_page_token`. */
|
|
7
7
|
const useList = (resource, { filter, orderBy, pageSize = 50, fields, startToken = "", enabled = true } = {}) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { keys } from "./query-keys.js";
|
|
2
2
|
import { useClient } from "./api-provider.js";
|
|
3
|
-
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
4
3
|
import { PreconditionFailedError } from "@protobase/client";
|
|
4
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
5
5
|
import { encodeKey } from "@protobase/schema";
|
|
6
6
|
//#region src/data/use-update-record.ts
|
|
7
7
|
/**
|
package/dist/data-grid/cells.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { Avatar } from "../primitives/avatar.js";
|
|
3
3
|
import { Badge } from "../primitives/badge.js";
|
|
4
|
-
import { formatDate, formatDateTime } from "../format/date.js";
|
|
5
|
-
import { userById } from "../mocks/users.js";
|
|
6
4
|
import { formatMoney } from "../format/money.js";
|
|
7
5
|
import { formatInt, formatPercent, formatSigned } from "../format/number.js";
|
|
8
|
-
import {
|
|
6
|
+
import { formatDate, formatDateTime } from "../format/date.js";
|
|
7
|
+
import { userById } from "../mocks/users.js";
|
|
9
8
|
import { Check } from "lucide-react";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
import { enumLabel } from "@protobase/schema";
|
|
11
11
|
//#region src/data-grid/cells.tsx
|
|
12
12
|
const muted = (text) => /* @__PURE__ */ jsx("span", {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
|
-
import { gridTemplate } from "./grid-template.js";
|
|
3
2
|
import { Checkbox } from "../primitives/checkbox.js";
|
|
3
|
+
import { gridTemplate } from "./grid-template.js";
|
|
4
4
|
import { isRightAligned } from "./column-spec.js";
|
|
5
5
|
import { HeaderMenu } from "./header-menu.js";
|
|
6
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
6
|
import { ArrowDown, ArrowUp, ChevronsUpDown } from "lucide-react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/data-grid/grid-header.tsx
|
|
9
9
|
const SortIcon = ({ sorted }) => {
|
|
10
10
|
if (sorted === "asc") return /* @__PURE__ */ jsx(ArrowUp, { className: "size-3 text-foreground" });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { formatInt } from "../format/number.js";
|
|
3
|
-
import { gridTemplate } from "./grid-template.js";
|
|
4
3
|
import { Checkbox } from "../primitives/checkbox.js";
|
|
4
|
+
import { gridTemplate } from "./grid-template.js";
|
|
5
5
|
import { isRightAligned } from "./column-spec.js";
|
|
6
6
|
import { Skeleton } from "../primitives/skeleton.js";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
2
|
import { MoreHorizontal } from "lucide-react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
//#region src/data-grid/header-menu.tsx
|
|
5
5
|
/** The "..." in a column header: for now, pinning the column to the left edge. */
|
|
6
6
|
const HeaderMenu = ({ header, pinned, onTogglePin }) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
3
|
import { MoreHorizontal } from "lucide-react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { createPortal } from "react-dom";
|
|
6
6
|
//#region src/data-grid/row-menu.tsx
|
|
7
7
|
/** The actions that apply to a row; a row with none gets no menu at all. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { diffStats, parseDiff } from "./diff.js";
|
|
3
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
3
|
import { FileCode2 } from "lucide-react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/diff-view/diff-view.tsx
|
|
6
6
|
const row = {
|
|
7
7
|
add: "bg-success-soft text-success-text",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
1
|
import { Lock, X } from "lucide-react";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
//#region src/filter-panel/active-filter-chips.tsx
|
|
4
4
|
const chipClass = "inline-flex h-6 items-center gap-1 rounded-full border border-primary-border bg-primary-soft pl-2.5 pr-1.5 text-xs font-medium text-primary-text";
|
|
5
5
|
const ActiveFilterChips = ({ chips, lockedLabels = [], onRemove, onClear, scroll }) => {
|
|
@@ -3,8 +3,8 @@ import { formatInt } from "../format/number.js";
|
|
|
3
3
|
import { Checkbox } from "../primitives/checkbox.js";
|
|
4
4
|
import { matchesSearch } from "./filter-state.js";
|
|
5
5
|
import { useState } from "react";
|
|
6
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
6
|
import { Search } from "lucide-react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/filter-panel/facet-group.tsx
|
|
9
9
|
const FacetGroup = ({ config, selected, onToggle }) => {
|
|
10
10
|
const [query, setQuery] = useState("");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
3
|
import { ChevronDown } from "lucide-react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/filter-panel/filter-menu.tsx
|
|
6
6
|
const FilterMenu = ({ label, activeCount, width = 240, defaultOpen = false, children }) => {
|
|
7
7
|
const [open, setOpen] = useState(defaultOpen);
|
package/dist/index.d.ts
CHANGED
|
@@ -8,12 +8,14 @@ import { useList } from "./data/use-list.js";
|
|
|
8
8
|
import { useRecord } from "./data/use-record.js";
|
|
9
9
|
import { useFacets, useHistogram, useSeries } from "./data/use-aggregates.js";
|
|
10
10
|
import { isConflict, useUpdateRecord } from "./data/use-update-record.js";
|
|
11
|
+
import { mergeUi } from "./app/pages/merge-ui.js";
|
|
11
12
|
import { LayoutRecord, useLayoutRecord } from "./app/pages/record-scope.js";
|
|
12
13
|
import { useApiToken } from "./auth/use-api-token.js";
|
|
13
14
|
import { AssistantDock, AssistantDockProps } from "./assistant-dock/assistant-dock.js";
|
|
14
15
|
import { PartView } from "./assistant-dock/part-view.js";
|
|
15
16
|
import { useAssistant } from "./assistant-dock/use-assistant.js";
|
|
16
17
|
import { useAssistantClient } from "./assistant-dock/use-assistant-client.js";
|
|
18
|
+
import { BackendData, BackendRefresh, useBackendData } from "./assistant-dock/use-backend-data.js";
|
|
17
19
|
import "./assistant-dock/index.js";
|
|
18
20
|
import { ChatMessage } from "./chat/chat-message.js";
|
|
19
21
|
import { ChatThread, ChatThreadProps } from "./chat/chat-thread.js";
|
|
@@ -28,4 +30,4 @@ import { Step, StepList, StepState } from "./step-list/step-list.js";
|
|
|
28
30
|
import { DiffView, DiffViewProps } from "./diff-view/diff-view.js";
|
|
29
31
|
import { DiffLine, diffStats, parseDiff } from "./diff-view/diff.js";
|
|
30
32
|
import { CompactTable, CompactTableProps } from "./compact-table/compact-table.js";
|
|
31
|
-
export { ActionCard, type ActionCardProps, type ActionContext, type ActionHandler, ApiProvider, type ApiProviderProps, App, type AppProps, AssistantDock, type AssistantDockProps, type CardAction, type CardTone, ChatMessage, ChatThread, type ChatThreadProps, CompactTable, type CompactTableProps, Composer, type ComposerProps, type DiffLine, DiffView, type DiffViewProps, FieldList, type LayoutRecord, type MetaData, PartView, type ProjectUi, SidePanel, type SidePanelProps, type Step, StepList, type StepState, defineUi, diffStats, isConflict, parseDiff, useApiToken, useAssistant, useAssistantClient, useClient, useFacets, useHistogram, useLayoutRecord, useList, useMeta, useRecord, useSeries, useUpdateRecord };
|
|
33
|
+
export { ActionCard, type ActionCardProps, type ActionContext, type ActionHandler, ApiProvider, type ApiProviderProps, App, type AppProps, AssistantDock, type AssistantDockProps, type BackendData, type BackendRefresh, type CardAction, type CardTone, ChatMessage, ChatThread, type ChatThreadProps, CompactTable, type CompactTableProps, Composer, type ComposerProps, type DiffLine, DiffView, type DiffViewProps, FieldList, type LayoutRecord, type MetaData, PartView, type ProjectUi, SidePanel, type SidePanelProps, type Step, StepList, type StepState, defineUi, diffStats, isConflict, mergeUi, parseDiff, useApiToken, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useHistogram, useLayoutRecord, useList, useMeta, useRecord, useSeries, useUpdateRecord };
|
package/dist/index.js
CHANGED
|
@@ -11,15 +11,17 @@ import { CompactTable } from "./compact-table/compact-table.js";
|
|
|
11
11
|
import { diffStats, parseDiff } from "./diff-view/diff.js";
|
|
12
12
|
import { DiffView } from "./diff-view/diff-view.js";
|
|
13
13
|
import { StepList } from "./step-list/step-list.js";
|
|
14
|
+
import { defineUi } from "./app/pages/project-ui.js";
|
|
14
15
|
import { PartView } from "./assistant-dock/part-view.js";
|
|
16
|
+
import { useBackendData } from "./assistant-dock/use-backend-data.js";
|
|
15
17
|
import { AssistantDock } from "./assistant-dock/assistant-dock.js";
|
|
16
18
|
import { useAssistant } from "./assistant-dock/use-assistant.js";
|
|
17
19
|
import { useAssistantClient } from "./assistant-dock/use-assistant-client.js";
|
|
18
20
|
import { useMeta } from "./data/use-meta.js";
|
|
19
21
|
import { useFacets, useHistogram, useSeries } from "./data/use-aggregates.js";
|
|
20
22
|
import { useLayoutRecord } from "./app/pages/record-scope.js";
|
|
21
|
-
import {
|
|
23
|
+
import { mergeUi } from "./app/pages/merge-ui.js";
|
|
22
24
|
import { useList } from "./data/use-list.js";
|
|
23
25
|
import { isConflict, useUpdateRecord } from "./data/use-update-record.js";
|
|
24
26
|
import { App } from "./app/app.js";
|
|
25
|
-
export { ActionCard, ApiProvider, App, AssistantDock, ChatMessage, ChatThread, CompactTable, Composer, DiffView, FieldList, PartView, SidePanel, StepList, defineUi, diffStats, isConflict, parseDiff, useApiToken, useAssistant, useAssistantClient, useClient, useFacets, useHistogram, useLayoutRecord, useList, useMeta, useRecord, useSeries, useUpdateRecord };
|
|
27
|
+
export { ActionCard, ApiProvider, App, AssistantDock, ChatMessage, ChatThread, CompactTable, Composer, DiffView, FieldList, PartView, SidePanel, StepList, defineUi, diffStats, isConflict, mergeUi, parseDiff, useApiToken, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useHistogram, useLayoutRecord, useList, useMeta, useRecord, useSeries, useUpdateRecord };
|
|
@@ -4,8 +4,8 @@ import { lineGrid } from "./columns.js";
|
|
|
4
4
|
import { LineRow } from "./line-row.js";
|
|
5
5
|
import { reorder } from "./reorder.js";
|
|
6
6
|
import { TotalsFooter } from "./totals-footer.js";
|
|
7
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
7
|
import { Plus } from "lucide-react";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import { DndContext, KeyboardSensor, MouseSensor, TouchSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
|
|
10
10
|
import { SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
11
11
|
//#region src/inline-child-table/inline-child-table.tsx
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
3
|
import { MoreHorizontal } from "lucide-react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/inline-child-table/line-menu.tsx
|
|
6
6
|
/** The "..." at the end of a line: the keyboard-and-screen-reader way to reorder, besides the drag handle. */
|
|
7
7
|
const LineMenu = ({ items, label }) => {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { formatMoney } from "../format/money.js";
|
|
2
3
|
import { lineTotalCents } from "../mocks/invoices.js";
|
|
3
4
|
import { lineGrid } from "./columns.js";
|
|
4
|
-
import { formatMoney } from "../format/money.js";
|
|
5
5
|
import { LineMenu } from "./line-menu.js";
|
|
6
6
|
import { NumberCell } from "./number-cell.js";
|
|
7
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
7
|
import { GripVertical } from "lucide-react";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import { useSortable } from "@dnd-kit/sortable";
|
|
10
10
|
//#region src/inline-child-table/line-row.tsx
|
|
11
11
|
const parseEuros = (text) => Math.round(Number(text.replace(",", ".").replace(/[^0-9.]/g, "")) * 100);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { invoiceTotals } from "../mocks/invoices.js";
|
|
2
1
|
import { formatMoney } from "../format/money.js";
|
|
2
|
+
import { invoiceTotals } from "../mocks/invoices.js";
|
|
3
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
//#region src/inline-child-table/totals-footer.tsx
|
|
5
5
|
const TotalsFooter = ({ lines, vatRate }) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { formatMoney } from "../format/money.js";
|
|
1
2
|
import { keys } from "../data/query-keys.js";
|
|
2
3
|
import { useClient } from "../data/api-provider.js";
|
|
3
4
|
import { humanize } from "./naming.js";
|
|
4
|
-
import { formatMoney } from "../format/money.js";
|
|
5
5
|
import { stateToText } from "./filter-string.js";
|
|
6
6
|
import { useHistogram } from "../data/use-aggregates.js";
|
|
7
7
|
import { fetchLabels } from "./relation-labels.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
3
2
|
import { Check, Minus } from "lucide-react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
//#region src/primitives/checkbox.tsx
|
|
5
5
|
const Checkbox = ({ checked, indeterminate, onChange, label, className }) => /* @__PURE__ */ jsx("button", {
|
|
6
6
|
type: "button",
|
package/dist/primitives/sheet.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useFocusTrap } from "../lib/use-focus-trap.js";
|
|
2
2
|
import { useRef } from "react";
|
|
3
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
3
|
import { X } from "lucide-react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/primitives/sheet.tsx
|
|
6
6
|
/** A panel that rises from the bottom of the screen, for the filter panel on phones. */
|
|
7
7
|
const Sheet = ({ title, open, onClose, children }) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
|
-
import { jsx } from "react/jsx-runtime";
|
|
3
2
|
import { Loader2 } from "lucide-react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
//#region src/primitives/spinner.tsx
|
|
5
5
|
const Spinner = ({ className }) => /* @__PURE__ */ jsx(Loader2, {
|
|
6
6
|
"aria-label": "Loading",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Avatar } from "../primitives/avatar.js";
|
|
2
2
|
import { Button } from "../primitives/button.js";
|
|
3
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
3
|
import { TriangleAlert } from "lucide-react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region src/record-view/field.tsx
|
|
6
6
|
const Field = ({ label, required, help, example, error, conflict, children }) => /* @__PURE__ */ jsxs("div", { children: [
|
|
7
7
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
1
|
import { ChevronRight } from "lucide-react";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
//#region src/record-view/record-sidebar.tsx
|
|
4
4
|
const MetadataList = ({ rows }) => /* @__PURE__ */ jsx("dl", {
|
|
5
5
|
className: "grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-[13px]",
|