@protobase/ui 0.19.0 → 0.19.2

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 (53) hide show
  1. package/dist/app/app.d.ts +2 -2
  2. package/dist/app/app.js +14 -9
  3. package/dist/app/custom-section.js +1 -1
  4. package/dist/app/displays/custom-display.js +1 -1
  5. package/dist/app/displays/map-display.js +1 -1
  6. package/dist/app/displays/record-card.js +1 -1
  7. package/dist/app/favicon.svg +1 -0
  8. package/dist/app/pages/merge-ui.d.ts +1 -1
  9. package/dist/app/pages/merge-ui.js +16 -4
  10. package/dist/app/pages/project-ui.d.ts +30 -5
  11. package/dist/app/pages/project-ui.js +3 -3
  12. package/dist/app/pages/render-node.js +1 -1
  13. package/dist/app/project-logo.js +17 -0
  14. package/dist/app-shell/app-shell.d.ts +1 -1
  15. package/dist/app-shell/app-shell.js +3 -2
  16. package/dist/app-shell/index.d.ts +1 -0
  17. package/dist/app-shell/logo.d.ts +11 -0
  18. package/dist/app-shell/logo.js +30 -32
  19. package/dist/app-shell/sidebar.d.ts +2 -1
  20. package/dist/app-shell/sidebar.js +3 -3
  21. package/dist/app-shell/wordmark-path.js +4 -0
  22. package/dist/assistant-dock/widget-view.js +1 -1
  23. package/dist/auth/auth-gate.js +7 -1
  24. package/dist/auth/auth-layout.d.ts +10 -0
  25. package/dist/auth/auth-provider.js +10 -0
  26. package/dist/auth/code-form.d.ts +29 -0
  27. package/dist/auth/resend-code-button.d.ts +11 -0
  28. package/dist/index.d.ts +9 -2
  29. package/dist/index.js +12 -5
  30. package/dist/package.js +1 -1
  31. package/dist/plugins/before-sign-in.js +49 -0
  32. package/dist/plugins/plugin-gate.js +8 -8
  33. package/dist/plugins/plugin-source.js +32 -0
  34. package/dist/plugins/plugin-ui.js +5 -3
  35. package/dist/plugins/use-plugin-loads.js +3 -4
  36. package/dist/primitives/button.d.ts +11 -0
  37. package/dist/primitives/input.d.ts +11 -0
  38. package/dist/primitives/spinner.d.ts +5 -0
  39. package/dist/shell/assets/app-Rbouc_gR.js +66 -0
  40. package/dist/shell/assets/{index-CLXqLf4p.js → index-Crd6_WsY.js} +1 -1
  41. package/dist/shell/assets/index-DaFzUWmg.css +2 -0
  42. package/dist/shell/assets/maplibre-Gu5hHDDK.js +814 -0
  43. package/dist/shell/assets/{record-map-Bvls7qdn.js → record-map-DQ0oyZff.js} +1 -1
  44. package/dist/shell/assets/records-map-Dp97bL3p.js +1 -0
  45. package/dist/shell/assets/shared-protobase-ui-DvvdY2OO.js +1 -0
  46. package/dist/shell/index.html +5 -4
  47. package/dist/shell/protobase.shell.json +1 -1
  48. package/package.json +6 -5
  49. package/dist/shell/assets/app-BS7ufaBT.js +0 -66
  50. package/dist/shell/assets/index-BLnOMDD8.css +0 -2
  51. package/dist/shell/assets/maplibre-r-WEXdlV.js +0 -814
  52. package/dist/shell/assets/records-map-iNBfciYy.js +0 -1
  53. package/dist/shell/assets/shared-protobase-ui-9PLdWR62.js +0 -1
package/dist/app/app.d.ts CHANGED
@@ -25,7 +25,7 @@ export type AppProps = {
25
25
  assistant?: AssistantClient;
26
26
  /** A ready-made client of the runtime endpoint, for stories and tests; used only when `/meta` names one. */
27
27
  runtime?: RuntimeClient;
28
- /** How the plugins `/meta` names are loaded, for stories and tests; the page's `fetch` and `import()` by default. */
28
+ /** How the plugins are loaded, for stories and tests; the page's `fetch` and `import()` by default. */
29
29
  pluginLoader?: PluginLoader;
30
30
  };
31
31
  /**
@@ -33,5 +33,5 @@ export type AppProps = {
33
33
  * then a sidebar from `/meta`, `/:page` composed pages, `/:resource` lists and `/:resource/:key` records. Fills its
34
34
  * parent, in the theme this browser chose, light, dark or the system's.
35
35
  */
36
- export declare const App: ({ auth, workspace, basePath, initialUrl, ...rest }: AppProps) => import("react").JSX.Element;
36
+ export declare const App: ({ auth, workspace, basePath, initialUrl, pluginLoader, ...rest }: AppProps) => import("react").JSX.Element;
37
37
  //#endregion
package/dist/app/app.js CHANGED
@@ -7,6 +7,9 @@ import { ApiProvider } from "../data/api-provider.js";
7
7
  import { Router, useRouter } from "./router.js";
8
8
  import { useSignInAddress } from "../auth/use-sign-in-address.js";
9
9
  import { managesOrganization, organizationMenuItems, organizationPageTitles } from "../organizations/organization-paths.js";
10
+ import { startErrorReporting } from "./error-reporting.js";
11
+ import { CustomBoundary } from "./pages/custom-boundary.js";
12
+ import { PluginSourceProvider } from "../plugins/plugin-source.js";
10
13
  import { AuthGate } from "../auth/auth-gate.js";
11
14
  import { isAdmin, userToShell } from "../auth/user-to-shell.js";
12
15
  import { useRecord } from "../data/use-record.js";
@@ -14,7 +17,6 @@ import { recordTitle } from "../live/model-helpers.js";
14
17
  import { humanize } from "../live/naming.js";
15
18
  import { ThemeProvider, useTheme } from "../theme/theme-provider.js";
16
19
  import { ToastProvider } from "../toasts/toast-provider.js";
17
- import { startErrorReporting } from "./error-reporting.js";
18
20
  import { useProjectUi } from "./pages/project-ui.js";
19
21
  import { AssistantButton, AssistantPanel } from "./assistant-shell.js";
20
22
  import { bottomBarFromMeta } from "./bottom-bar-from-meta.js";
@@ -22,7 +24,6 @@ import { breadcrumbFor } from "./breadcrumb-for.js";
22
24
  import { sourceStatusOf } from "../data/with-sources.js";
23
25
  import { MetaGate, Notice, useAdminMeta } from "./meta-gate.js";
24
26
  import { useAppAddress } from "./use-app-address.js";
25
- import { CustomBoundary } from "./pages/custom-boundary.js";
26
27
  import { CreatePage } from "./create/create-page.js";
27
28
  import { ListPage } from "./list-page.js";
28
29
  import { MembersPage } from "../organizations/members-page.js";
@@ -35,6 +36,7 @@ import { useActiveRole } from "../roles/use-active-role.js";
35
36
  import { userMenuFromMeta } from "./user-menu-from-meta.js";
36
37
  import { navFromMeta } from "./nav-from-meta.js";
37
38
  import { ComposedPage } from "./pages/composed-page.js";
39
+ import { ProjectLogo } from "./project-logo.js";
38
40
  import { PluginGate, usePluginsOff } from "../plugins/plugin-gate.js";
39
41
  import { PluginNotice } from "../plugins/plugin-notice.js";
40
42
  import { RecordPage } from "./record-page.js";
@@ -177,6 +179,7 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
177
179
  onSignOut: user && (() => void signOut()),
178
180
  signIn: user ? void 0 : signIn.link(),
179
181
  workspace: current?.organization?.name ?? workspace,
182
+ logo: ProjectLogo,
180
183
  navGroups: groups,
181
184
  bottomBar: bottomBarFromMeta(meta, basePath, place),
182
185
  userMenu: [
@@ -244,7 +247,7 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
244
247
  ]
245
248
  });
246
249
  };
247
- const Authenticated = ({ baseUrl, client, sources, workspace, sidebarMode, ui, assistant, runtime, pluginLoader }) => {
250
+ const Authenticated = ({ baseUrl, client, sources, workspace, sidebarMode, ui, assistant, runtime }) => {
248
251
  const { state, session, markSignedOut } = useAuth();
249
252
  return /* @__PURE__ */ jsx(ApiProvider, {
250
253
  baseUrl,
@@ -254,7 +257,6 @@ const Authenticated = ({ baseUrl, client, sources, workspace, sidebarMode, ui, a
254
257
  onUnauthenticated: markSignedOut,
255
258
  children: /* @__PURE__ */ jsx(MetaGate, { children: /* @__PURE__ */ jsx(ToastProvider, { children: /* @__PURE__ */ jsx(PluginGate, {
256
259
  ui,
257
- ...pluginLoader && { loader: pluginLoader },
258
260
  children: /* @__PURE__ */ jsx(Routes, {
259
261
  workspace,
260
262
  sidebarMode,
@@ -269,16 +271,19 @@ const Authenticated = ({ baseUrl, client, sources, workspace, sidebarMode, ui, a
269
271
  * then a sidebar from `/meta`, `/:page` composed pages, `/:resource` lists and `/:resource/:key` records. Fills its
270
272
  * parent, in the theme this browser chose, light, dark or the system's.
271
273
  */
272
- const App = ({ auth, workspace, basePath, initialUrl, ...rest }) => /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(AuthProvider, {
274
+ const App = ({ auth, workspace, basePath, initialUrl, pluginLoader, ...rest }) => /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(AuthProvider, {
273
275
  session: auth,
274
276
  children: /* @__PURE__ */ jsx(Router, {
275
277
  basePath,
276
278
  initialUrl,
277
- children: /* @__PURE__ */ jsx(AuthGate, {
278
- workspace,
279
- children: /* @__PURE__ */ jsx(Authenticated, {
279
+ children: /* @__PURE__ */ jsx(PluginSourceProvider, {
280
+ ...pluginLoader && { loader: pluginLoader },
281
+ children: /* @__PURE__ */ jsx(AuthGate, {
280
282
  workspace,
281
- ...rest
283
+ children: /* @__PURE__ */ jsx(Authenticated, {
284
+ workspace,
285
+ ...rest
286
+ })
282
287
  })
283
288
  })
284
289
  })
@@ -1,6 +1,6 @@
1
- import { useProjectUi } from "./pages/project-ui.js";
2
1
  import { Problem } from "./pages/problem.js";
3
2
  import { CustomBoundary } from "./pages/custom-boundary.js";
3
+ import { useProjectUi } from "./pages/project-ui.js";
4
4
  import { Section } from "../record-view/section.js";
5
5
  import { SidebarCard } from "../record-view/record-sidebar.js";
6
6
  import { RecordScope } from "./pages/record-scope.js";
@@ -1,6 +1,6 @@
1
- import { useProjectUi } from "../pages/project-ui.js";
2
1
  import { Problem } from "../pages/problem.js";
3
2
  import { CustomBoundary } from "../pages/custom-boundary.js";
3
+ import { useProjectUi } from "../pages/project-ui.js";
4
4
  import { ListDisplayProvider } from "./display-context.js";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import { unmarkFieldRefs } from "@protobase/schema/internal";
@@ -1,10 +1,10 @@
1
1
  import { cn } from "../../lib/cn.js";
2
2
  import { Button } from "../../primitives/button.js";
3
+ import { Problem } from "../pages/problem.js";
3
4
  import { humanize } from "../../live/naming.js";
4
5
  import { mapCards } from "./list-displays.js";
5
6
  import { useAdminMeta } from "../meta-gate.js";
6
7
  import { ErrorBanner } from "../error-banner.js";
7
- import { Problem } from "../pages/problem.js";
8
8
  import { useDebounced } from "../../lib/use-debounced.js";
9
9
  import { RecordCard } from "./record-card.js";
10
10
  import { boundsAround, worldArea } from "../map/map-area.js";
@@ -1,4 +1,5 @@
1
1
  import { Badge } from "../../primitives/badge.js";
2
+ import { CustomBoundary } from "../pages/custom-boundary.js";
2
3
  import { recordKey } from "../../live/model-helpers.js";
3
4
  import { humanize } from "../../live/naming.js";
4
5
  import { useProjectUi } from "../pages/project-ui.js";
@@ -6,7 +7,6 @@ import { displayValue } from "../../live/display-value.js";
6
7
  import { amountParts, cardFields, filledAmount, filledMeta } from "./list-displays.js";
7
8
  import { AppLink } from "../use-app-address.js";
8
9
  import { shownWidget } from "../field-types/shown-widget.js";
9
- import { CustomBoundary } from "../pages/custom-boundary.js";
10
10
  import { WidgetPart } from "../field-types/widget-part.js";
11
11
  import { namedIcon } from "../named-icon.js";
12
12
  import { FavoriteButton } from "../saved/favorite-button.js";
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><style>.c{fill:#FF5A1F}.r{fill:#14161A}@media (prefers-color-scheme:dark){.c{fill:#FF6A33}.r{fill:#F1F0EA}}</style><path class="c" fill-rule="evenodd" d="M0 5a5 5 0 0 1 5-5h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zM4 5a1 1 0 0 1 1-1h5v6H4z"/><path class="r" d="M17 0h10a5 5 0 0 1 5 5v22a5 5 0 0 1-5 5H5a5 5 0 0 1-5-5V17h17z"/></svg>
@@ -3,7 +3,7 @@ import { ProjectUi } from "./project-ui.js";
3
3
  /**
4
4
  * One UI config from several, each after the ones it `extends`, the last being the app's own: components, action
5
5
  * handlers and field types are all kept, and one name defined by two configs is an error naming both; every shell slot
6
- * is drawn, the extended configs' first.
6
+ * and `beforeSignIn` component is drawn, the extended configs' first, except the logo, the last config's to set one.
7
7
  */
8
8
  export declare const mergeUi: (...uis: ProjectUi[]) => ProjectUi;
9
9
  //#endregion
@@ -25,10 +25,18 @@ const slot = (slots) => {
25
25
  const All = () => slots.map((Slot, index) => /* @__PURE__ */ jsx(Slot, {}, index));
26
26
  return All;
27
27
  };
28
+ const around = (wrappers) => {
29
+ if (wrappers.length <= 1) return wrappers[0];
30
+ const All = ({ children, ...given }) => wrappers.reduceRight((inner, Wrapper) => /* @__PURE__ */ jsx(Wrapper, {
31
+ ...given,
32
+ children: inner
33
+ }), children);
34
+ return All;
35
+ };
28
36
  /**
29
37
  * One UI config from several, each after the ones it `extends`, the last being the app's own: components, action
30
38
  * handlers and field types are all kept, and one name defined by two configs is an error naming both; every shell slot
31
- * is drawn, the extended configs' first.
39
+ * and `beforeSignIn` component is drawn, the extended configs' first, except the logo, the last config's to set one.
32
40
  */
33
41
  const mergeUi = (...uis) => {
34
42
  const named = uis.flatMap((ui, index) => flatten(ui, ui.name ?? `UI config ${index + 1}`));
@@ -37,16 +45,20 @@ const mergeUi = (...uis) => {
37
45
  const fields = union("The field type", named, (ui) => ui.fields);
38
46
  const actionSlot = slot(named.flatMap(({ ui }) => ui.shell?.actions ? [ui.shell.actions] : []));
39
47
  const rightPanel = slot(named.flatMap(({ ui }) => ui.shell?.rightPanel ? [ui.shell.rightPanel] : []));
48
+ const logo = named.findLast(({ ui }) => ui.shell?.logo)?.ui.shell?.logo;
49
+ const beforeSignIn = around(named.flatMap(({ ui }) => ui.beforeSignIn ? [ui.beforeSignIn] : []));
40
50
  const name = uis.at(-1)?.name;
41
51
  return {
42
52
  ...name && { name },
43
53
  components,
44
54
  actions,
45
55
  ...Object.keys(fields).length > 0 && { fields },
46
- ...(actionSlot || rightPanel) && { shell: {
56
+ ...(actionSlot || rightPanel || logo) && { shell: {
47
57
  ...actionSlot && { actions: actionSlot },
48
- ...rightPanel && { rightPanel }
49
- } }
58
+ ...rightPanel && { rightPanel },
59
+ ...logo && { logo }
60
+ } },
61
+ ...beforeSignIn && { beforeSignIn }
50
62
  };
51
63
  };
52
64
  //#endregion
@@ -1,5 +1,6 @@
1
+ import { ShellLogoProps } from "../../app-shell/logo.js";
1
2
  import { FieldWidget } from "../field-types/field-widget.js";
2
- import { ComponentType } from "react";
3
+ import { ComponentType, ReactNode } from "react";
3
4
  import { Client } from "@protobase/client";
4
5
  import { AppAddress } from "@protobase/schema";
5
6
  //#region src/app/pages/project-ui.d.ts
@@ -19,6 +20,21 @@ export type ActionContext = {
19
20
  navigate: (to: AppAddress) => void;
20
21
  };
21
22
  export type ActionHandler = (context: ActionContext) => void | Promise<void>;
23
+ /** What a plugin's `beforeSignIn` component gets from the app, drawn around the pages someone signed out sees. */
24
+ export type BeforeSignInProps = {
25
+ /** What the app shows someone signed out: the sign-in page, or the app a visitor browses. */
26
+ children: ReactNode;
27
+ /**
28
+ * Enters the app with a session something other than the sign-in page started, such as an endpoint that traded a
29
+ * one-time code for the session cookie: reads the session again and shows the app signed in. Resolves `false`, and
30
+ * changes nothing, when there is no session.
31
+ */
32
+ signedIn: () => Promise<boolean>;
33
+ /** The query parameters of the page's address, such as a one-time code a link carries. */
34
+ params: URLSearchParams;
35
+ /** Takes these query parameters, such as a used one-time code, out of the address, in place of it in the tab's history. */
36
+ removeParams: (names: string[]) => void;
37
+ };
22
38
  /**
23
39
  * The project's own React code for composed pages: custom components by the name their layout declares
24
40
  * (`component('UsageChart')`) or a widget part names, handlers for named actions that have no built-in behaviour,
@@ -33,11 +49,20 @@ export type ProjectUi = {
33
49
  actions?: Record<string, ActionHandler>;
34
50
  /** Field types by name, each with the storage types it shows and its components (see `FieldWidget`). */
35
51
  fields?: Record<string, FieldWidget>;
36
- /** Rendered in the shell on every page: `actions` at the end of the top bar, `rightPanel` beside the page. */
52
+ /**
53
+ * Rendered in the shell on every page: `actions` at the end of the top bar, `rightPanel` beside the page, and `logo`
54
+ * at the top of the sidebar in place of Protobase's (see `ShellLogoProps`).
55
+ */
37
56
  shell?: {
38
57
  actions?: ComponentType;
39
58
  rightPanel?: ComponentType;
59
+ logo?: ComponentType<ShellLogoProps>;
40
60
  };
61
+ /**
62
+ * Drawn around the pages someone signed out sees, only for a plugin the app loads before sign-in (`beforeSignIn` in
63
+ * `options.plugins`): its code runs for them, and it can sign them in (see `BeforeSignInProps`).
64
+ */
65
+ beforeSignIn?: ComponentType<BeforeSignInProps>;
41
66
  };
42
67
  /** A `protobase.ui.tsx` module as `import * as` gives it: components exported by name, the rest as the default export. */
43
68
  export type UiModule = {
@@ -47,9 +72,9 @@ export type UiModule = {
47
72
  /** Typed identity, for `export default defineUi({ extends, components, actions })` in `protobase.ui.tsx`. */
48
73
  export declare const defineUi: (ui: ProjectUi) => ProjectUi;
49
74
  /**
50
- * Typed identity, for `export default definePlugin({ name, components, actions, fields, shell })` in a plugin's
51
- * `protobase.ui.tsx`: a plugin is a UI config the app loads at runtime, its components exported by name as in a
52
- * project's. A project's own protobase.ui.tsx is one too, which `protobase build` builds as the plugin `app`.
75
+ * Typed identity, for `export default definePlugin({ name, components, actions, fields, shell, beforeSignIn })` in a
76
+ * plugin's `protobase.ui.tsx`: a plugin is a UI config the app loads at runtime, its components exported by name as in
77
+ * a project's. A project's own protobase.ui.tsx is one too, which `protobase build` builds as the plugin `app`.
53
78
  */
54
79
  export declare const definePlugin: (plugin: ProjectUi) => ProjectUi;
55
80
  //#endregion
@@ -3,9 +3,9 @@ import { createContext, useContext } from "react";
3
3
  /** Typed identity, for `export default defineUi({ extends, components, actions })` in `protobase.ui.tsx`. */
4
4
  const defineUi = (ui) => ui;
5
5
  /**
6
- * Typed identity, for `export default definePlugin({ name, components, actions, fields, shell })` in a plugin's
7
- * `protobase.ui.tsx`: a plugin is a UI config the app loads at runtime, its components exported by name as in a
8
- * project's. A project's own protobase.ui.tsx is one too, which `protobase build` builds as the plugin `app`.
6
+ * Typed identity, for `export default definePlugin({ name, components, actions, fields, shell, beforeSignIn })` in a
7
+ * plugin's `protobase.ui.tsx`: a plugin is a UI config the app loads at runtime, its components exported by name as in
8
+ * a project's. A project's own protobase.ui.tsx is one too, which `protobase build` builds as the plugin `app`.
9
9
  */
10
10
  const definePlugin = (plugin) => plugin;
11
11
  /** The project's UI, merged with the UI configs it extends and the plugins loaded; `PluginGate` or `ProjectUiProvider` provides it. */
@@ -1,6 +1,6 @@
1
- import { useProjectUi } from "./project-ui.js";
2
1
  import { Problem } from "./problem.js";
3
2
  import { CustomBoundary } from "./custom-boundary.js";
3
+ import { useProjectUi } from "./project-ui.js";
4
4
  import { ActionBlock } from "./blocks/action.js";
5
5
  import { CardBlock } from "./blocks/card.js";
6
6
  import { CardRowBlock } from "./blocks/card-row.js";
@@ -0,0 +1,17 @@
1
+ import { Logo } from "../app-shell/logo.js";
2
+ import { CustomBoundary } from "./pages/custom-boundary.js";
3
+ import { useProjectUi } from "./pages/project-ui.js";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/app/project-logo.tsx
6
+ /** The project's `shell.logo` at the top of the sidebar, or Protobase's while it has none or when it throws. */
7
+ const ProjectLogo = (props) => {
8
+ const Own = useProjectUi().shell?.logo;
9
+ if (!Own) return /* @__PURE__ */ jsx(Logo, { ...props });
10
+ return /* @__PURE__ */ jsx(CustomBoundary, {
11
+ name: "shell.logo",
12
+ fallback: /* @__PURE__ */ jsx(Logo, { ...props }),
13
+ children: /* @__PURE__ */ jsx(Own, { ...props })
14
+ });
15
+ };
16
+ //#endregion
17
+ export { ProjectLogo };
@@ -4,4 +4,4 @@ import "./nav.js";
4
4
  import "./profile-menu.js";
5
5
  import "./global-search.js";
6
6
  import "./breadcrumb.js";
7
- import { ReactNode } from "react";
7
+ import { ComponentType, ReactNode } from "react";
@@ -9,7 +9,7 @@ import { useEffect, useRef, useState } from "react";
9
9
  import { jsx, jsxs } from "react/jsx-runtime";
10
10
  //#region src/app-shell/app-shell.tsx
11
11
  const drawerId = "app-drawer";
12
- const AppShell = ({ sidebarMode, activeItem, breadcrumb, user, workspace, actions, rightPanel, forceExpanded, profileMenuOpen, navGroups, userMenu, bottomBar, onNavigate, onSignOut, signIn, organizations, roles, theme, search, pageScroll: scrollsByDefault = false, className, children }) => {
12
+ const AppShell = ({ sidebarMode, activeItem, breadcrumb, user, workspace, logo, actions, rightPanel, forceExpanded, profileMenuOpen, navGroups, userMenu, bottomBar, onNavigate, onSignOut, signIn, organizations, roles, theme, search, pageScroll: scrollsByDefault = false, className, children }) => {
13
13
  const desktop = useIsDesktop();
14
14
  const [drawerOpen, setDrawerOpen] = useState(false);
15
15
  useEffect(() => {
@@ -37,7 +37,8 @@ const AppShell = ({ sidebarMode, activeItem, breadcrumb, user, workspace, action
37
37
  signIn,
38
38
  organizations,
39
39
  roles,
40
- theme
40
+ theme,
41
+ logo
41
42
  });
42
43
  return /* @__PURE__ */ jsx(PageScrollContext.Provider, {
43
44
  value: setPageScroll,
@@ -4,6 +4,7 @@ import "./profile-menu.js";
4
4
  import { SidebarMode, sidebarModes } from "./sidebar-mode.js";
5
5
  import "./global-search.js";
6
6
  import "./breadcrumb.js";
7
+ import { LogoMark, ShellLogoProps } from "./logo.js";
7
8
  import "./app-shell.js";
8
9
  import "./page-header.js";
9
10
  import "./page-scroll.js";
@@ -0,0 +1,11 @@
1
+ //#region src/app-shell/logo.d.ts
2
+ /** The Protobase mark, the Corner: the outlined corner is what an app describes, the solid rest what Protobase runs. */
3
+ export declare const LogoMark: ({ className }: {
4
+ className?: string;
5
+ }) => import("react").JSX.Element;
6
+ /** What the logo at the top of the sidebar gets: `compact` when the sidebar shows only icons, and the workspace's name. */
7
+ export type ShellLogoProps = {
8
+ compact: boolean;
9
+ workspace?: string;
10
+ };
11
+ //#endregion
@@ -1,46 +1,44 @@
1
1
  import { cn } from "../lib/cn.js";
2
+ import { wordmarkPath } from "./wordmark-path.js";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
4
  //#region src/app-shell/logo.tsx
5
+ const corner = "fill-[#ff5a1f] dark:fill-[#ff6a33]";
6
+ const solid = "fill-[#14161a] dark:fill-[#f1f0ea]";
7
+ /** The Protobase mark, the Corner: the outlined corner is what an app describes, the solid rest what Protobase runs. */
4
8
  const LogoMark = ({ className }) => /* @__PURE__ */ jsxs("svg", {
5
- viewBox: "0 0 24 24",
9
+ viewBox: "0 0 32 32",
6
10
  className: cn("size-7 shrink-0", className),
7
11
  "aria-hidden": true,
8
- children: [
9
- /* @__PURE__ */ jsx("rect", {
10
- width: "24",
11
- height: "24",
12
- rx: "6",
13
- className: "fill-primary"
14
- }),
15
- /* @__PURE__ */ jsx("path", {
16
- d: "M7 17V7h5.2a3 3 0 0 1 0 6H7",
17
- fill: "none",
18
- className: "stroke-primary-foreground",
19
- strokeWidth: "2",
20
- strokeLinecap: "round",
21
- strokeLinejoin: "round"
22
- }),
23
- /* @__PURE__ */ jsx("path", {
24
- d: "M12.5 17h4.5",
25
- className: "stroke-primary-foreground",
26
- strokeOpacity: "0.55",
27
- strokeWidth: "2",
28
- strokeLinecap: "round"
29
- })
30
- ]
12
+ children: [/* @__PURE__ */ jsx("path", {
13
+ className: corner,
14
+ fillRule: "evenodd",
15
+ d: "M0 5a5 5 0 0 1 5-5h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zM4 5a1 1 0 0 1 1-1h5v6H4z"
16
+ }), /* @__PURE__ */ jsx("path", {
17
+ className: solid,
18
+ d: "M17 0h10a5 5 0 0 1 5 5v22a5 5 0 0 1-5 5H5a5 5 0 0 1-5-5V17h17z"
19
+ })]
20
+ });
21
+ /** The name "protobase", outlined as the brand draws it rather than set in a font. */
22
+ const Wordmark = ({ className }) => /* @__PURE__ */ jsx("svg", {
23
+ viewBox: "54.8 -720 3743.8 925",
24
+ className: cn("h-3.5 w-auto", className),
25
+ role: "img",
26
+ "aria-label": "Protobase",
27
+ children: /* @__PURE__ */ jsx("path", {
28
+ className: solid,
29
+ d: wordmarkPath
30
+ })
31
31
  });
32
- const Logo = ({ showName, workspace }) => /* @__PURE__ */ jsxs("div", {
32
+ /** Protobase's logo at the top of the sidebar, the workspace's name under the wordmark, or the mark alone when compact. */
33
+ const Logo = ({ compact, workspace }) => /* @__PURE__ */ jsxs("div", {
33
34
  className: "flex items-center gap-2.5 overflow-hidden",
34
- children: [/* @__PURE__ */ jsx(LogoMark, {}), showName && /* @__PURE__ */ jsxs("div", {
35
- className: "min-w-0 leading-tight",
36
- children: [/* @__PURE__ */ jsx("div", {
37
- className: "text-[13px] font-semibold tracking-tight",
38
- children: "Protobase"
39
- }), workspace && /* @__PURE__ */ jsx("div", {
35
+ children: [/* @__PURE__ */ jsx(LogoMark, {}), !compact && /* @__PURE__ */ jsxs("div", {
36
+ className: "flex min-w-0 flex-col gap-0.5 leading-tight",
37
+ children: [/* @__PURE__ */ jsx(Wordmark, { className: "self-start" }), workspace && /* @__PURE__ */ jsx("div", {
40
38
  className: "truncate text-[11px] text-muted-foreground",
41
39
  children: workspace
42
40
  })]
43
41
  })]
44
42
  });
45
43
  //#endregion
46
- export { Logo, LogoMark };
44
+ export { Logo, LogoMark, Wordmark };
@@ -1,3 +1,4 @@
1
1
  import "../primitives/route-link.js";
2
2
  import "./nav.js";
3
- import "./profile-menu.js";
3
+ import "./profile-menu.js";
4
+ import { ComponentType } from "react";
@@ -10,7 +10,7 @@ import { SidebarGroup } from "./sidebar-group.js";
10
10
  import { LogIn } from "lucide-react";
11
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
12
  //#region src/app-shell/sidebar.tsx
13
- const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenuOpen, groups = navGroups, userMenu, onNavigate, onSignOut, signIn, organizations, roles, theme, sticky }) => {
13
+ const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenuOpen, groups = navGroups, userMenu, onNavigate, onSignOut, signIn, organizations, roles, theme, sticky, logo: SidebarLogo = Logo }) => {
14
14
  const expanding = mode === "icon-expand";
15
15
  const expanded = expanding && forceExpanded;
16
16
  const inline = isTextMode(mode) || expanding;
@@ -22,8 +22,8 @@ const Sidebar = ({ mode, activeItem, user, workspace, forceExpanded, profileMenu
22
22
  children: [
23
23
  /* @__PURE__ */ jsx("div", {
24
24
  className: cn("flex h-12 shrink-0 items-center px-3.5", mode === "icon-label" && "justify-center px-0"),
25
- children: /* @__PURE__ */ jsx(Logo, {
26
- showName: isTextMode(mode) || expanding,
25
+ children: /* @__PURE__ */ jsx(SidebarLogo, {
26
+ compact: !inline,
27
27
  workspace
28
28
  })
29
29
  }),
@@ -0,0 +1,4 @@
1
+ //#region src/app-shell/wordmark-path.ts
2
+ const wordmarkPath = "M312.3 9.9Q257 9.9 217.1 -21.1Q177.3 -52.1 168.4 -108.8L181.1 -110.3V205H54.8V-510H176.7V-404.5L168.3 -407Q179.7 -460.8 221.2 -490.4Q262.7 -520 319.8 -520Q376.2 -520 417 -488.4Q457.8 -456.8 479.8 -398.6Q501.8 -340.5 501.8 -259.9Q501.8 -177.6 478.3 -116.8Q454.9 -55.9 412.5 -23Q370.1 9.9 312.3 9.9ZM274.4 -92.6Q320.2 -92.6 345.8 -133.8Q371.4 -175.1 371.4 -256.4Q371.4 -337.7 345.7 -377.6Q320.1 -417.5 274 -417.5Q228.6 -417.5 202.7 -376.4Q176.9 -335.3 176.9 -254Q176.9 -173.3 202.7 -132.9Q228.4 -92.6 274.4 -92.6Z M549.8 0V-510H671.7V-388.3H676.1V0H549.8ZM676.1 -286.5 661.3 -386.2Q677.9 -455.2 711.8 -487.6Q745.7 -520 794.3 -520Q809 -520 818 -516.6V-394.2Q814.2 -395.6 807.1 -396.1Q800 -396.6 789.4 -396.6Q734.6 -396.6 705.3 -370.5Q676.1 -344.4 676.1 -286.5Z M1043.8 10Q974.7 10 922.6 -23.6Q870.6 -57.2 842.1 -117.5Q813.6 -177.8 813.6 -257Q813.6 -336.5 842.2 -395.5Q870.8 -454.5 922.5 -487.2Q974.2 -520 1043.8 -520Q1113.7 -520 1165.4 -487.2Q1217.1 -454.5 1245.6 -395.5Q1274 -336.5 1274 -257Q1274 -177.8 1245.5 -117.5Q1217 -57.2 1165.3 -23.6Q1113.6 10 1043.8 10ZM1043.8 -90.5Q1073.5 -90.5 1095.9 -108.9Q1118.2 -127.3 1130.9 -164.4Q1143.6 -201.5 1143.6 -257.5Q1143.6 -339.8 1116 -379.7Q1088.4 -419.5 1043.8 -419.5Q999.3 -419.5 971.6 -379.5Q944 -339.5 944 -257.5Q944 -201.5 956.7 -164.4Q969.4 -127.3 991.8 -108.9Q1014.3 -90.5 1043.8 -90.5Z M1509 10.2Q1427.2 10.2 1387.6 -29.2Q1348 -68.6 1348 -147.7V-613.8L1474.2 -666.1V-146.7Q1474.2 -118.4 1489.9 -104.3Q1505.6 -90.3 1539.2 -90.3Q1552 -90.3 1562.7 -92.5Q1573.3 -94.8 1582.5 -98.7V-1.9Q1571.6 4 1552 7.1Q1532.4 10.2 1509 10.2ZM1270.1 -411V-510H1580.6V-411H1270.1Z M1810.8 10Q1741.7 10 1689.6 -23.6Q1637.6 -57.2 1609.1 -117.5Q1580.6 -177.8 1580.6 -257Q1580.6 -336.5 1609.2 -395.5Q1637.8 -454.5 1689.5 -487.2Q1741.2 -520 1810.8 -520Q1880.7 -520 1932.4 -487.2Q1984.1 -454.5 2012.6 -395.5Q2041 -336.5 2041 -257Q2041 -177.8 2012.5 -117.5Q1984 -57.2 1932.3 -23.6Q1880.6 10 1810.8 10ZM1810.8 -90.5Q1840.5 -90.5 1862.9 -108.9Q1885.2 -127.3 1897.9 -164.4Q1910.6 -201.5 1910.6 -257.5Q1910.6 -339.8 1883 -379.7Q1855.4 -419.5 1810.8 -419.5Q1766.3 -419.5 1738.6 -379.5Q1711 -339.5 1711 -257.5Q1711 -201.5 1723.7 -164.4Q1736.4 -127.3 1758.8 -108.9Q1781.3 -90.5 1810.8 -90.5Z M2343.3 9.9Q2288 9.9 2247.7 -21.1Q2207.4 -52.1 2198.5 -108.8L2205.8 -110.3V0H2085.8V-720H2212.1V-404.5L2203.7 -407Q2215.1 -460.8 2255.6 -490.4Q2296.2 -520 2351 -520Q2407.3 -520 2448 -488.4Q2488.8 -456.8 2510.8 -398.6Q2532.8 -340.5 2532.8 -259.9Q2532.8 -177.6 2509.3 -116.8Q2485.9 -55.9 2443.5 -23Q2401.1 9.9 2343.3 9.9ZM2305.4 -92.6Q2350.7 -92.6 2376.6 -133.8Q2402.4 -175.1 2402.4 -256.4Q2402.4 -337.7 2376.5 -377.6Q2350.6 -417.5 2305 -417.5Q2260.5 -417.5 2234.2 -376.4Q2207.9 -335.3 2207.9 -254Q2207.9 -173.3 2234.1 -132.9Q2260.3 -92.6 2305.4 -92.6Z M2834 0Q2828 -19.1 2825.7 -41.7Q2823.3 -64.2 2823.3 -91.8H2818.6V-356Q2818.6 -391.9 2803.1 -409.1Q2787.5 -426.3 2756.2 -426.3Q2724.2 -426.3 2706.4 -408.7Q2688.7 -391.2 2686.3 -358.9H2571.2Q2574.6 -429.6 2625.4 -474.8Q2676.1 -520 2763.5 -520Q2850.2 -520 2896.1 -476.5Q2941.9 -432.9 2941.9 -352.2V-91.8Q2941.9 -69.5 2944.7 -46.7Q2947.5 -23.8 2954.1 0H2834ZM2690.6 10Q2631.9 10 2595.4 -26.3Q2558.9 -62.5 2558.9 -124.5Q2558.9 -185 2594.3 -224Q2629.8 -263 2704.5 -287.6L2845.4 -337.4V-248.7L2753.4 -219.3Q2716.5 -206.1 2699.3 -186.6Q2682.2 -167.2 2682.2 -139Q2682.2 -112.4 2697.5 -98.4Q2712.9 -84.3 2739.4 -84.3Q2773.2 -84.3 2795.9 -104.1Q2818.6 -123.9 2818.6 -153.1L2829.9 -85.8Q2812.6 -37.4 2776.8 -13.7Q2741 10 2690.6 10Z M3168 10Q3080.6 10 3030.1 -34.2Q2979.6 -78.4 2974.7 -158.1H3082.4Q3086.3 -120 3108.9 -100.5Q3131.5 -80.9 3169.8 -80.9Q3202 -80.9 3218.9 -93.9Q3235.8 -106.9 3235.8 -131.3Q3235.8 -151.1 3224.3 -165.6Q3212.9 -180.1 3178.9 -195.4L3111.2 -224.8Q3053 -247.7 3023.8 -284.2Q2994.5 -320.8 2994.5 -373.9Q2994.5 -440.1 3040.1 -480Q3085.7 -520 3162.8 -520Q3242.2 -520 3288.4 -480Q3334.6 -440 3339.5 -367.9H3231.8Q3228.6 -399.7 3211.3 -414.4Q3194 -429.1 3164 -429.1Q3136.1 -429.1 3120.5 -417Q3104.8 -405 3104.8 -382.7Q3104.8 -364.2 3117.7 -349.8Q3130.6 -335.5 3162.4 -321.6L3236.6 -289.6Q3288.6 -269.2 3317.4 -229.2Q3346.1 -189.1 3346.1 -136.9Q3346.1 -69 3299.2 -29.5Q3252.3 10 3168 10Z M3587.8 10Q3520 10 3469.2 -23.3Q3418.5 -56.6 3390.5 -116.5Q3362.6 -176.3 3362.6 -255.8Q3362.6 -336.4 3390.9 -395.7Q3419.3 -454.9 3470.7 -487.5Q3522 -520 3591.1 -520Q3656.6 -520 3702.7 -489.8Q3748.8 -459.7 3773.7 -403.1Q3798.6 -346.5 3798.6 -266.5Q3798.6 -253.7 3798 -242.5Q3797.5 -231.3 3795.8 -221.4H3440V-306.9H3709L3681.8 -277.9Q3681.6 -355.2 3657.7 -390.7Q3633.8 -426.3 3589.3 -426.3Q3541.7 -426.3 3515.6 -384Q3489.5 -341.7 3489.5 -255Q3489.5 -168.4 3515 -126.1Q3540.5 -83.7 3590.6 -83.7Q3623.7 -83.7 3644.6 -102Q3665.5 -120.3 3673.8 -156.3H3788.9Q3772.2 -81.6 3721.5 -35.8Q3670.8 10 3587.8 10Z";
3
+ //#endregion
4
+ export { wordmarkPath };
@@ -1,5 +1,5 @@
1
- import { ActionCard } from "../action-card/action-card.js";
2
1
  import { reportRenderError } from "../app/error-reporting.js";
2
+ import { ActionCard } from "../action-card/action-card.js";
3
3
  import { useProjectUi } from "../app/pages/project-ui.js";
4
4
  import { Component } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
@@ -19,6 +19,7 @@ import { TwoFactorPage } from "./two-factor-page.js";
19
19
  import { useSignInAddress } from "./use-sign-in-address.js";
20
20
  import { readInvitationLink, withoutInvitationLink } from "../organizations/invitation-link.js";
21
21
  import { InvitationPage } from "../organizations/invitation-page.js";
22
+ import { BeforeSignIn } from "../plugins/before-sign-in.js";
22
23
  import { useCallback, useEffect, useState } from "react";
23
24
  import { jsx, jsxs } from "react/jsx-runtime";
24
25
  import { signInPath } from "@protobase/schema";
@@ -62,9 +63,14 @@ const Redirect = ({ to }) => {
62
63
  * browse, and the right page otherwise: the step of the sign-in route the address names, or the page an emailed reset
63
64
  * link, an invitation link or a staff link opened. Someone signed out without a visitor's browsing goes to the sign-in
64
65
  * route, and signing in comes back to the page the route was opened from. An invited person with an account signs in
65
- * first and comes back to the invitation.
66
+ * first and comes back to the invitation. The plugins the app loads before sign-in draw around what someone signed
67
+ * out sees (see `BeforeSignIn`), while the gate keeps its state.
66
68
  */
67
69
  const AuthGate = ({ workspace, children }) => {
70
+ const page = useAuthPage(workspace, children);
71
+ return /* @__PURE__ */ jsx(BeforeSignIn, { children: page });
72
+ };
73
+ const useAuthPage = (workspace, children) => {
68
74
  const { state, recheck, passwordReset, signOut, signInProviders, canSignUp, browse } = useAuth();
69
75
  const { path, params, basePath, navigate } = useRouter();
70
76
  const { address, next, open, link, leave } = useSignInAddress();
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/auth/auth-layout.d.ts
3
+ export type AuthLayoutProps = {
4
+ title: string;
5
+ description?: string;
6
+ children: ReactNode;
7
+ };
8
+ /** The frame of the sign-in and first-run pages: logo, title and a card. */
9
+ export declare const AuthLayout: ({ title, description, children }: AuthLayoutProps) => import("react").JSX.Element;
10
+ //#endregion
@@ -21,6 +21,7 @@ const AuthProvider = ({ session: given, children }) => {
21
21
  const [staffSignIn, setStaffSignIn] = useState();
22
22
  const [organizations, setOrganizations] = useState();
23
23
  const [anonymous, setAnonymous] = useState(false);
24
+ const [beforeSignIn, setBeforeSignIn] = useState([]);
24
25
  const signedOutState = useCallback(() => anonymous ? { kind: "visitor" } : { kind: "signed-out" }, [anonymous]);
25
26
  const markSignedOut = useCallback(() => setState((current) => current.kind === "signed-in" ? { kind: "signed-out" } : current), []);
26
27
  const signedOut = useRef(markSignedOut);
@@ -54,6 +55,7 @@ const AuthProvider = ({ session: given, children }) => {
54
55
  setStaffSignIn(status.staffSignIn);
55
56
  setOrganizations(status.organizations);
56
57
  setAnonymous(Boolean(status.anonymous));
58
+ setBeforeSignIn(status.plugins ?? []);
57
59
  if (status.needsAdmin) return setState({ kind: "needs-admin" });
58
60
  const user = await session.session();
59
61
  return user ? enter(user) : setState({ kind: status.anonymous ? "visitor" : "signed-out" });
@@ -79,6 +81,13 @@ const AuthProvider = ({ session: given, children }) => {
79
81
  ...organizations && { organizations },
80
82
  markSignedOut,
81
83
  recheck,
84
+ beforeSignIn,
85
+ adoptSession: async () => {
86
+ const user = await session.session();
87
+ if (!user) return false;
88
+ await enter(user);
89
+ return true;
90
+ },
82
91
  ...anonymous && { browse: () => {
83
92
  setState((current) => current.kind === "signed-out" ? { kind: "visitor" } : current);
84
93
  } },
@@ -116,6 +125,7 @@ const AuthProvider = ({ session: given, children }) => {
116
125
  staffSignIn,
117
126
  organizations,
118
127
  anonymous,
128
+ beforeSignIn,
119
129
  signedOutState,
120
130
  markSignedOut,
121
131
  recheck,
@@ -0,0 +1,29 @@
1
+ import { ReactNode } from "react";
2
+ //#region src/auth/code-form.d.ts
3
+ export type CodeFormProps = {
4
+ title: string;
5
+ description?: string;
6
+ /** Above the code: which address it went to, and how long it works. */
7
+ explanation: ReactNode;
8
+ /** The button that checks the code, which a complete code presses by itself. */
9
+ submitLabel: string;
10
+ /** Called with the code; the page shows `error` when it is not right. */
11
+ onSubmit: (code: string) => void;
12
+ /** Sends a new code to the same address. */
13
+ onResend: () => void;
14
+ /** Back to the step that asked for the address, for another one. */
15
+ onChangeAddress: () => void;
16
+ changeAddressLabel: string;
17
+ busy?: boolean;
18
+ error?: string;
19
+ /** Shown once a new code is on its way. */
20
+ notice?: string;
21
+ /** Seconds before "Send a new code" works, after each code; 30 by default. */
22
+ resendCooldown?: number;
23
+ };
24
+ /**
25
+ * 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
26
+ * to another address. Nothing else is asked for until the code is right.
27
+ */
28
+ export declare const CodeForm: ({ title, description, explanation, submitLabel, onSubmit, onResend, onChangeAddress, changeAddressLabel, busy, error, notice, resendCooldown }: CodeFormProps) => import("react").JSX.Element;
29
+ //#endregion
@@ -0,0 +1,11 @@
1
+ //#region src/auth/resend-code-button.d.ts
2
+ export type ResendCodeButtonProps = {
3
+ /** Sends a new code; the button waits `cooldown` seconds again after it. */
4
+ onResend: () => void;
5
+ /** Seconds to wait after a code went out before another is asked for; 30 by default. */
6
+ cooldown?: number;
7
+ disabled?: boolean;
8
+ };
9
+ /** "Send a new code", counting down first: the screen opens right after a code went out, and so does every resend. */
10
+ export declare const ResendCodeButton: ({ onResend, cooldown, disabled }: ResendCodeButtonProps) => import("react").JSX.Element;
11
+ //#endregion