@rebasepro/app 0.14.1 → 0.14.2-canary.g27a129e

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.
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import { User, DataDriver, DataSourceDefinition, StorageSource, StorageSourceDefinition, DatabaseAdmin, RebaseClient } from "@rebasepro/types";
3
- import { Locale, AuthController, AnalyticsEvent, UserConfigurationPersistence, RebaseContext, EntityLinkBuilder, RebasePlugin, SlotContribution, PropertyConfig, EntityCustomView, EntityAction, RebaseTranslations, ComponentOverrideMap } from "@rebasepro/admin-types";
3
+ import { Locale, AuthController, AnalyticsEvent, UserConfigurationPersistence, RebaseContext, EntityLinkBuilder, RebasePlugin, SlotContribution, PropertyConfig, EntityCustomView, CollectionCustomView, EntityAction, RebaseTranslations, ComponentOverrideMap } from "@rebasepro/admin-types";
4
4
  /**
5
5
  * A data source registered on `<Rebase>`. Extends the shared
6
6
  * {@link DataSourceDefinition} with the frontend {@link DataDriver} used for
@@ -258,6 +258,11 @@ export type RebaseProps<USER extends User, DB = unknown> = {
258
258
  * Entity Views
259
259
  */
260
260
  entityViews?: EntityCustomView[];
261
+ /**
262
+ * Custom collection view modes, available to every collection by `key`.
263
+ * A collection opts into one by naming that key in `admin.customViews`.
264
+ */
265
+ collectionViews?: CollectionCustomView[];
261
266
  /**
262
267
  * Entity Actions
263
268
  */
package/dist/index.es.js CHANGED
@@ -1,7 +1,7 @@
1
- import React, { createContext, lazy, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
2
- import { Alert, AlertCircleIcon, AlertTriangleIcon, AppWindow, ArrowDownIcon, ArrowLeftIcon, ArrowRightLeftIcon, ArrowUpDownIcon, ArrowUpIcon, Avatar, BooleanSwitch, Button, CalendarIcon, Card, CenteredView, CheckCircle2Icon, CheckIcon, Checkbox, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, Chip, CircleDotIcon, CircleUserIcon, CircularProgress, ColumnsIcon, Container, Dialog, DialogActions, DialogContent, DialogTitle, DollarSignIcon, ErrorBoundary, FileIcon, FileTextIcon, FilterChip, FilterIcon, FolderIcon, FolderKanbanIcon, IconButton, KanbanIcon, KanbanView, LanguagesIcon, LayoutGridIcon, ListIcon, ListPlusIcon, ListTodoIcon, LoadingButton, LogOutIcon, LucideIconByName, MailIcon, Menu, MenuItem, MessageCircleIcon, MoonIcon, MultiSelect, MultiSelectItem, PanelLeftIcon, Paper, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlusIcon, Popover, RefreshCwIcon, SearchBar, Select, SelectItem, Separator, SettingsIcon, Skeleton, StickyNoteIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableRow, Tabs, TagIcon, TextField, ToggleButtonGroup, Tooltip, Trash2Icon, TrendingUpIcon, TypeIcon, Typography, UserIcon, UserPlus, UsersIcon, VideoIcon, Wand2Icon, WrenchIcon, XIcon, cls, colorClassesMapping, coolIconKeys, defaultBorderMixin, getColorSchemeForKey, getColorSchemeForSeed, iconKeys, iconSize } from "@rebasepro/ui";
1
+ import React, { createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
2
+ import { Alert, AlertCircleIcon, AlertTriangleIcon, AppWindow, ArrowDownIcon, ArrowLeftIcon, ArrowRightLeftIcon, ArrowUpDownIcon, ArrowUpIcon, Avatar, BooleanSwitch, Button, CalendarIcon, Card, CenteredView, CheckCircle2Icon, CheckIcon, Checkbox, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, Chip, CircleDotIcon, CircleUserIcon, CircularProgress, ColumnsIcon, Container, Dialog, DialogActions, DialogContent, DialogTitle, DollarSignIcon, ErrorBoundary, FileIcon, FileTextIcon, FilterChip, FilterIcon, FolderIcon, FolderKanbanIcon, IconButton, KanbanIcon, KanbanView, LanguagesIcon, LayoutGridIcon, ListIcon, ListPlusIcon, ListTodoIcon, LoadingButton, LogOutIcon, LucideIconByName, MailIcon, Menu, MenuItem, MessageCircleIcon, MoonIcon, MultiSelect, MultiSelectItem, PanelLeftIcon, Paper, PenLineIcon, PencilIcon, PhoneIcon, PinIcon, PlusIcon, Popover, RefreshCwIcon, SearchBar, Select, SelectItem, Separator, SettingsIcon, Skeleton, StickyNoteIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableRow, Tabs, TagIcon, TextField, ToggleButtonGroup, Tooltip, Trash2Icon, TrendingUpIcon, TypeIcon, Typography, UserIcon, UserPlus, UsersIcon, VideoIcon, Wand2Icon, WrenchIcon, XIcon, cls, colorClassesMapping, coolIconKeys, defaultBorderMixin, getColorSchemeForKey, getColorSchemeForSeed, iconKeys, iconSize, lazyChunk } from "@rebasepro/ui";
3
3
  import { ALL_WHERE_FILTER_OPS, DEFAULT_DATA_SOURCE_KEY, DEFAULT_FILTERABLE_RELATION_KINDS, DEFAULT_STORAGE_SOURCE_KEY, EntityReference, EntityRelation, GeoPoint, RebaseApiError, Vector, getDataSourceCapabilities, isLazyComponentRef, isRelationalCollectionConfig } from "@rebasepro/types";
4
- import { UNRENDERED_SLOTS, resolveAdminCollection } from "@rebasepro/admin-types";
4
+ import { RENAMED_SLOTS, UNRENDERED_SLOTS, resolveAdminCollection } from "@rebasepro/admin-types";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { SnackbarProvider as SnackbarProvider$1, useSnackbar } from "notistack";
7
7
  import { buildRebaseData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, evaluateCondition, getChildViewDeclaringProperties, getChildViewRelationPropertyKeys, getEntityChildViews, getLabelOrConfigFrom, getPrimaryKeys, getSubcollections, isPropertyBuilder, normalizeOrderBy, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, serializeOrderBy, stripCollectionPath, wrapAsEntityData, wrapAsSdkData } from "@rebasepro/common";
@@ -2299,7 +2299,7 @@ function useResolvedComponent(ref) {
2299
2299
  function getOrCreateLazy(key, loader) {
2300
2300
  const cached = lazyCache.get(key);
2301
2301
  if (cached) return cached;
2302
- const LazyComponent = lazy(loader);
2302
+ const LazyComponent = lazyChunk(loader);
2303
2303
  lazyCache.set(key, LazyComponent);
2304
2304
  return LazyComponent;
2305
2305
  }
@@ -7794,6 +7794,7 @@ function UIReferenceView() {
7794
7794
  "h4",
7795
7795
  "h5",
7796
7796
  "h6",
7797
+ "lead",
7797
7798
  "subtitle1",
7798
7799
  "subtitle2",
7799
7800
  "body1",
@@ -7828,6 +7829,72 @@ function UIReferenceView() {
7828
7829
  "\""
7829
7830
  ]
7830
7831
  }, c))
7832
+ }),
7833
+ /* @__PURE__ */ jsxs("div", {
7834
+ className: cls("mt-8 pt-6 border-t", defaultBorderMixin),
7835
+ children: [
7836
+ /* @__PURE__ */ jsx(Typography, {
7837
+ variant: "subtitle2",
7838
+ className: "block mb-1",
7839
+ children: "Data tiers"
7840
+ }),
7841
+ /* @__PURE__ */ jsxs(Typography, {
7842
+ variant: "body2",
7843
+ color: "secondary",
7844
+ className: "block mb-5 max-w-[65ch]",
7845
+ children: [
7846
+ "For values that are looked up rather than read: a field name, a measurement, a headline figure. ",
7847
+ /* @__PURE__ */ jsx("code", {
7848
+ className: "font-mono text-xs",
7849
+ children: "mono"
7850
+ }),
7851
+ " and",
7852
+ " ",
7853
+ /* @__PURE__ */ jsx("code", {
7854
+ className: "font-mono text-xs",
7855
+ children: "stat"
7856
+ }),
7857
+ " both carry",
7858
+ " ",
7859
+ /* @__PURE__ */ jsx("code", {
7860
+ className: "font-mono text-xs",
7861
+ children: "tabular-nums"
7862
+ }),
7863
+ ", so a column of them keeps its decimal points aligned and a live counter does not jitter as its digits change width."
7864
+ ]
7865
+ }),
7866
+ /* @__PURE__ */ jsx("div", {
7867
+ className: "flex flex-wrap items-start gap-x-12 gap-y-5",
7868
+ children: [
7869
+ ["Region", "europe-west1"],
7870
+ ["Took", "32.2s"],
7871
+ ["Last run", "16/08/2026, 05:30:32"]
7872
+ ].map(([label, value]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
7873
+ variant: "micro",
7874
+ component: "div",
7875
+ color: "secondary",
7876
+ className: "block",
7877
+ children: label
7878
+ }), /* @__PURE__ */ jsx(Typography, {
7879
+ variant: "mono",
7880
+ component: "div",
7881
+ className: "block mt-1 text-sm",
7882
+ children: value
7883
+ })] }, label))
7884
+ }),
7885
+ /* @__PURE__ */ jsxs("div", {
7886
+ className: "mt-6",
7887
+ children: [/* @__PURE__ */ jsx("span", {
7888
+ className: "text-xs text-surface-400 font-mono",
7889
+ children: "stat"
7890
+ }), /* @__PURE__ */ jsx(Typography, {
7891
+ variant: "stat",
7892
+ component: "div",
7893
+ className: "block mt-1",
7894
+ children: "1,284"
7895
+ })]
7896
+ })
7897
+ ]
7831
7898
  })
7832
7899
  ]
7833
7900
  }),
@@ -9527,6 +9594,21 @@ function warnAboutHalfConfiguredOAuth(provider, clientId, backendProviders) {
9527
9594
  console.warn(clientId ? `[Rebase] ${provider} login is hidden: the login view has a client id, but the backend reports no "${provider}" provider. Set ${envVar} (and ${provider.toUpperCase()}_CLIENT_SECRET) on the server and restart it.` : `[Rebase] ${provider} login is hidden: the backend has a "${provider}" provider, but the login view was given no client id. Pass it to the login view (e.g. VITE_${envVar}) and restart the frontend so the new env var is picked up.`);
9528
9595
  }
9529
9596
  /**
9597
+ * Whether a provider's error code means "the visitor backed out" rather than
9598
+ * "the sign-in failed". Closing the popup is an answer, not a fault, and
9599
+ * showing it in red reads as a broken login.
9600
+ */
9601
+ function isUserCancellation(code) {
9602
+ if (!code) return false;
9603
+ return [
9604
+ "access_denied",
9605
+ "popup_closed",
9606
+ "popup_closed_by_user",
9607
+ "user_cancel",
9608
+ "immediate_failed"
9609
+ ].includes(code);
9610
+ }
9611
+ /**
9530
9612
  * Generic login view component that works with any AuthControllerExtended.
9531
9613
  * Feature-detects capabilities to show/hide login methods.
9532
9614
  * @group Core
@@ -9537,6 +9619,7 @@ function LoginView({ logo, authController, noUserComponent, disableSignupScreen
9537
9619
  const [mode, setMode] = useState("buttons");
9538
9620
  const [fadeIn, setFadeIn] = useState(false);
9539
9621
  const [viewVisible, setViewVisible] = useState(true);
9622
+ const [providerError, setProviderError] = useState(null);
9540
9623
  const [newsletterOptIn, setNewsletterOptIn] = useState(false);
9541
9624
  const authControllerRef = useRef(authController);
9542
9625
  authControllerRef.current = authController;
@@ -9554,6 +9637,7 @@ function LoginView({ logo, authController, noUserComponent, disableSignupScreen
9554
9637
  onNewsletterOptIn(email);
9555
9638
  }, [newsletterOptIn, onNewsletterOptIn]);
9556
9639
  const switchMode = (newMode) => {
9640
+ setProviderError(null);
9557
9641
  setViewVisible(false);
9558
9642
  setTimeout(() => {
9559
9643
  setMode(newMode);
@@ -9593,10 +9677,12 @@ function LoginView({ logo, authController, noUserComponent, disableSignupScreen
9593
9677
  window.history.replaceState({}, document.title, cleanUrl);
9594
9678
  if (result.status === "error") {
9595
9679
  console.error(`OAuth sign-in failed: ${result.error}`);
9680
+ if (!isUserCancellation(result.error)) setProviderError(`Sign-in failed: ${result.error}`);
9596
9681
  return;
9597
9682
  }
9598
9683
  if (result.status === "mismatch") {
9599
9684
  console.error("Ignoring an OAuth code that does not match a sign-in this browser started.");
9685
+ setProviderError("This sign-in could not be verified as one you started in this browser. Please try again.");
9600
9686
  return;
9601
9687
  }
9602
9688
  if (authController.oauthLogin) authController.oauthLogin(result.provider, {
@@ -9618,6 +9704,8 @@ function LoginView({ logo, authController, noUserComponent, disableSignupScreen
9618
9704
  alt: "Logo"
9619
9705
  });
9620
9706
  else logoComponent = /* @__PURE__ */ jsx(RebaseLogo, {});
9707
+ const controllerError = authController.authProviderError && !authController.user ? authController.authProviderError instanceof Error ? authController.authProviderError.message : String(authController.authProviderError) : null;
9708
+ const buttonsErrorMessage = providerError ?? controllerError;
9621
9709
  let notAllowedMessage;
9622
9710
  if (notAllowedError) if (typeof notAllowedError === "string") notAllowedMessage = notAllowedError;
9623
9711
  else if (notAllowedError instanceof Error) notAllowedMessage = notAllowedError.message;
@@ -9689,6 +9777,7 @@ function LoginView({ logo, authController, noUserComponent, disableSignupScreen
9689
9777
  mode === "buttons" && /* @__PURE__ */ jsxs("div", {
9690
9778
  className: "w-full flex flex-col gap-3 mt-2",
9691
9779
  children: [
9780
+ buttonsErrorMessage && /* @__PURE__ */ jsx(ErrorView, { error: buttonsErrorMessage }),
9692
9781
  (title || subtitle) && /* @__PURE__ */ jsxs("div", {
9693
9782
  className: "text-center mb-2",
9694
9783
  children: [title && /* @__PURE__ */ jsx(Typography, {
@@ -9729,7 +9818,8 @@ function LoginView({ logo, authController, noUserComponent, disableSignupScreen
9729
9818
  disabled,
9730
9819
  googleClientId,
9731
9820
  authController,
9732
- onSignedIn: () => subscribeIfOptedIn(authControllerRef.current.user?.email)
9821
+ onSignedIn: () => subscribeIfOptedIn(authControllerRef.current.user?.email),
9822
+ onError: setProviderError
9733
9823
  }),
9734
9824
  hasGitHubLogin && githubClientId && /* @__PURE__ */ jsx(GitHubLoginButton, {
9735
9825
  disabled,
@@ -9838,10 +9928,12 @@ var GoogleIcon = () => /* @__PURE__ */ jsxs("svg", {
9838
9928
  })
9839
9929
  ]
9840
9930
  });
9841
- function GoogleLoginButton({ disabled, googleClientId, authController, onSignedIn }) {
9931
+ function GoogleLoginButton({ disabled, googleClientId, authController, onSignedIn, onError }) {
9842
9932
  const codeClientRef = useRef(null);
9843
9933
  const onSignedInRef = useRef(onSignedIn);
9844
9934
  onSignedInRef.current = onSignedIn;
9935
+ const onErrorRef = useRef(onError);
9936
+ onErrorRef.current = onError;
9845
9937
  useEffect(() => {
9846
9938
  if (!authController.googleLogin) return;
9847
9939
  const google = window.google;
@@ -9853,8 +9945,10 @@ function GoogleLoginButton({ disabled, googleClientId, authController, onSignedI
9853
9945
  callback: async (response) => {
9854
9946
  if (response.error || !response.code) {
9855
9947
  console.error("Google login error:", response.error);
9948
+ if (!isUserCancellation(response.error)) onErrorRef.current?.(response.error ? `Google sign-in failed: ${response.error}` : "Google sign-in failed. Please try again.");
9856
9949
  return;
9857
9950
  }
9951
+ onErrorRef.current?.(null);
9858
9952
  try {
9859
9953
  await authController.googleLogin({
9860
9954
  code: response.code,
@@ -9870,8 +9964,10 @@ function GoogleLoginButton({ disabled, googleClientId, authController, onSignedI
9870
9964
  const handleClick = () => {
9871
9965
  if (!codeClientRef.current) {
9872
9966
  console.error("Google Sign-In not loaded");
9967
+ onErrorRef.current?.("Google Sign-In could not be loaded. Check your connection or any script blockers, then try again.");
9873
9968
  return;
9874
9969
  }
9970
+ onErrorRef.current?.(null);
9875
9971
  codeClientRef.current.requestCode();
9876
9972
  };
9877
9973
  return /* @__PURE__ */ jsx(LoginButton, {
@@ -16469,13 +16565,23 @@ function buildResources(translations) {
16469
16565
  * @group Core
16470
16566
  */
16471
16567
  function Rebase(props) {
16472
- const { children, entityLinkBuilder, userConfigPersistence, dateTimeFormat, locale, client, authController: authControllerProp, storageSource: storageSourceProp, dataSources: dataSourcesProp, storageSources: storageSourcesProp, databaseAdmin, plugins: pluginsProp, slots: directSlots = [], onAnalyticsEvent, propertyConfigs, entityViews, entityActions, effectiveRoleController, apiUrl, translations, components: componentsProp } = props;
16568
+ const { children, entityLinkBuilder, userConfigPersistence, dateTimeFormat, locale, client, authController: authControllerProp, storageSource: storageSourceProp, dataSources: dataSourcesProp, storageSources: storageSourcesProp, databaseAdmin, plugins: pluginsProp, slots: directSlots = [], onAnalyticsEvent, propertyConfigs, entityViews, collectionViews, entityActions, effectiveRoleController, apiUrl, translations, components: componentsProp } = props;
16473
16569
  const plugins = pluginsProp;
16474
16570
  if (plugins) {
16475
16571
  const keys = plugins.map((p) => p.key);
16476
16572
  if (new Set(keys).size !== keys.length) console.error("Duplicate plugin keys detected:", keys.filter((k, i) => keys.indexOf(k) !== i));
16477
16573
  }
16478
- const resolvedSlots = useMemo(() => [...directSlots, ...(plugins ?? []).flatMap((p) => p.slots ?? [])], [directSlots, plugins]);
16574
+ const resolvedSlots = useMemo(() => [...directSlots, ...(plugins ?? []).flatMap((p) => p.slots ?? [])].map((contribution) => {
16575
+ const current = RENAMED_SLOTS[contribution.slot];
16576
+ return current ? {
16577
+ ...contribution,
16578
+ slot: current
16579
+ } : contribution;
16580
+ }), [directSlots, plugins]);
16581
+ useEffect(() => {
16582
+ const registered = [...directSlots, ...(plugins ?? []).flatMap((p) => p.slots ?? [])];
16583
+ for (const legacy of new Set(registered.map((s) => s.slot).filter((slot) => slot in RENAMED_SLOTS))) console.warn(`[Rebase] The "${legacy}" slot has been renamed to "${RENAMED_SLOTS[legacy]}". The contribution was redirected and still renders, but the old name will be removed in a future major version.`);
16584
+ }, [directSlots, plugins]);
16479
16585
  useEffect(() => {
16480
16586
  const dead = new Set(UNRENDERED_SLOTS);
16481
16587
  const registered = new Set(resolvedSlots.map((s) => s.slot).filter((slot) => dead.has(slot)));
@@ -16632,6 +16738,7 @@ function Rebase(props) {
16632
16738
  plugins,
16633
16739
  resolvedSlots,
16634
16740
  entityViews: entityViews ?? [],
16741
+ collectionViews: collectionViews ?? [],
16635
16742
  entityActions: entityActions ?? [],
16636
16743
  propertyConfigs: propertyConfigs ?? {},
16637
16744
  components: componentsProp
@@ -16642,6 +16749,7 @@ function Rebase(props) {
16642
16749
  plugins,
16643
16750
  resolvedSlots,
16644
16751
  entityViews,
16752
+ collectionViews,
16645
16753
  entityActions,
16646
16754
  propertyConfigs,
16647
16755
  componentsProp